From f5c7d11f0e115b487aeb1859b006933b8effb666 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 12 Aug 2017 21:57:47 -0400 Subject: [PATCH] Add marshalling metadata lookup & adapt iOS for new APIs. --- core/c/mpw-algorithm.c | 2 +- core/c/mpw-marshall-util.c | 2 +- core/c/mpw-marshall-util.h | 2 +- core/c/mpw-marshall.c | 139 ++++- core/c/mpw-marshall.h | 27 +- core/c/mpw-types.c | 16 +- core/c/mpw-types.h | 6 +- core/c/mpw-util.c | 2 + .../project.pbxproj | 12 + .../project.pbxproj | 9 +- platform-darwin/Scripts/build_libjson-c-ios | 1 - platform-darwin/Scripts/build_libjson-c-osx | 1 - platform-darwin/Scripts/build_libsodium-ios | 1 - platform-darwin/Scripts/build_libsodium-osx | 1 - platform-darwin/Source/MPAlgorithm.h | 59 +- platform-darwin/Source/MPAlgorithmV0.m | 506 +++++++----------- platform-darwin/Source/MPAlgorithmV1.m | 2 +- platform-darwin/Source/MPAlgorithmV2.m | 2 +- platform-darwin/Source/MPAlgorithmV3.m | 2 +- platform-darwin/Source/MPAppDelegate_Key.m | 4 +- platform-darwin/Source/MPAppDelegate_Store.h | 19 +- platform-darwin/Source/MPAppDelegate_Store.m | 467 ++++++---------- platform-darwin/Source/MPEntities.h | 6 +- platform-darwin/Source/MPEntities.m | 24 +- platform-darwin/Source/MPTypes.h | 12 +- platform-darwin/Source/MPTypes.m | 1 + platform-darwin/Source/Mac/MPSiteModel.m | 16 +- .../Source/Mac/MPSitesWindowController.m | 4 +- .../Source/iOS/MPEmergencyViewController.m | 20 +- .../Source/iOS/MPPreferencesViewController.m | 58 +- platform-darwin/Source/iOS/MPSiteCell.m | 20 +- .../Source/iOS/MPTypeViewController.h | 4 +- .../Source/iOS/MPTypeViewController.m | 42 +- platform-darwin/Source/iOS/MPiOSAppDelegate.m | 183 ++++--- platform-independent/cli-c/cli/mpw-cli.c | 35 +- 35 files changed, 821 insertions(+), 886 deletions(-) diff --git a/core/c/mpw-algorithm.c b/core/c/mpw-algorithm.c index 63b925cf..d48eb9b8 100644 --- a/core/c/mpw-algorithm.c +++ b/core/c/mpw-algorithm.c @@ -102,7 +102,7 @@ const char *mpw_siteResult( return NULL; } } - else if (resultType & MPResultTypeClassState) { + else if (resultType & MPResultTypeClassStateful) { switch (algorithmVersion) { case MPAlgorithmVersion0: return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam ); diff --git a/core/c/mpw-marshall-util.c b/core/c/mpw-marshall-util.c index cfec3907..100592ee 100644 --- a/core/c/mpw-marshall-util.c +++ b/core/c/mpw-marshall-util.c @@ -21,7 +21,7 @@ #include "mpw-marshall-util.h" #include "mpw-util.h" -char *mpw_get_token(char **in, char *eol, char *delim) { +char *mpw_get_token(const char **in, const char *eol, char *delim) { // Skip leading spaces. for (; **in == ' '; ++*in); diff --git a/core/c/mpw-marshall-util.h b/core/c/mpw-marshall-util.h index e1e61061..e996d2a9 100644 --- a/core/c/mpw-marshall-util.h +++ b/core/c/mpw-marshall-util.h @@ -30,7 +30,7 @@ * The input string reference is advanced beyond the token delimitor if one is found. * @return A new string containing the token or NULL if the delim wasn't found before eol. */ char *mpw_get_token( - char **in, char *eol, char *delim); + const char **in, const char *eol, char *delim); /** Convert an RFC 3339 time string into epoch time. */ time_t mpw_mktime( const char *time); diff --git a/core/c/mpw-marshall.c b/core/c/mpw-marshall.c index d8dfe87e..57c9ee30 100644 --- a/core/c/mpw-marshall.c +++ b/core/c/mpw-marshall.c @@ -89,6 +89,20 @@ MPMarshalledQuestion *mpw_marshal_question( return question; } +bool mpw_marshal_info_free( + MPMarshallInfo *info) { + + if (!info) + return true; + + bool success = true; + success &= mpw_free_string( info->fullName ); + success &= mpw_free_string( info->keyID ); + success &= mpw_free( info, sizeof( MPMarshallInfo ) ); + + return success; +} + bool mpw_marshal_free( MPMarshalledUser *user) { @@ -313,6 +327,9 @@ bool mpw_marshall_write( char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) { switch (outFormat) { + case MPMarshallFormatNone: + *error = (MPMarshallError){ .type = MPMarshallSuccess }; + return false; case MPMarshallFormatFlat: return mpw_marshall_write_flat( out, user, error ); case MPMarshallFormatJSON: @@ -323,10 +340,63 @@ bool mpw_marshall_write( } } +static void mpw_marshall_read_flat_info( + const char *in, MPMarshallInfo *info) { + + info->algorithm = MPAlgorithmVersionCurrent; + + // Parse import data. + bool headerStarted = false; + for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) { + + // Comment or header + if (*positionInLine == '#') { + ++positionInLine; + + if (!headerStarted) { + if (*positionInLine == '#') + // ## starts header + headerStarted = true; + // Comment before header + continue; + } + if (*positionInLine == '#') + // ## ends header + break; + + // Header + char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" ); + char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" ); + if (!headerName || !headerValue) + continue; + + if (strcmp( headerName, "Algorithm" ) == 0) + info->algorithm = (MPAlgorithmVersion)atoi( headerValue ); + if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0) + info->fullName = strdup( headerValue ); + if (strcmp( headerName, "Key ID" ) == 0) + info->keyID = strdup( headerValue ); + if (strcmp( headerName, "Passwords" ) == 0) + info->redacted = strcmp( headerValue, "VISIBLE" ) != 0; + if (strcmp( headerName, "Date" ) == 0) + info->date = mpw_mktime( headerValue ); + + mpw_free_string( headerName ); + mpw_free_string( headerValue ); + continue; + } + } +} + static MPMarshalledUser *mpw_marshall_read_flat( - char *in, const char *masterPassword, MPMarshallError *error) { + const char *in, const char *masterPassword, MPMarshallError *error) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; + if (!in || !strlen( in )) { + error->type = MPMarshallErrorStructure; + error->description = mpw_str( "No input data." ); + return NULL; + } // Parse import data. MPMasterKey masterKey = NULL; @@ -336,7 +406,7 @@ static MPMarshalledUser *mpw_marshall_read_flat( MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1; MPResultType defaultType = MPResultTypeDefault; bool headerStarted = false, headerEnded = false, importRedacted = false; - for (char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) { + for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) { // Comment or header if (*positionInLine == '#') { @@ -541,10 +611,39 @@ static MPMarshalledUser *mpw_marshall_read_flat( return user; } +static void mpw_marshall_read_json_info( + const char *in, MPMarshallInfo *info) { + + // Parse JSON. + enum json_tokener_error json_error = json_tokener_success; + json_object *json_file = json_tokener_parse_verbose( in, &json_error ); + if (!json_file || json_error != json_tokener_success) + return; + + // Section: "export" + int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 ); + if (fileFormat < 1) + return; + info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true ); + info->date = mpw_mktime( mpw_get_json_string( json_file, "export.date", NULL ) ); + + // Section: "user" + info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent ); + info->fullName = strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) ); + info->keyID = strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) ); + + json_object_put( json_file ); +} + static MPMarshalledUser *mpw_marshall_read_json( - char *in, const char *masterPassword, MPMarshallError *error) { + const char *in, const char *masterPassword, MPMarshallError *error) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; + if (!in || !strlen( in )) { + error->type = MPMarshallErrorStructure; + error->description = mpw_str( "No input data." ); + return NULL; + } // Parse JSON. enum json_tokener_error json_error = json_tokener_success; @@ -683,10 +782,33 @@ static MPMarshalledUser *mpw_marshall_read_json( return user; } +MPMarshallInfo *mpw_marshall_read_info( + const char *in) { + + MPMarshallInfo *info = malloc( sizeof( MPMarshallInfo ) ); + *info = (MPMarshallInfo){ .format = MPMarshallFormatNone }; + + if (in && strlen( in )) { + if (in[0] == '#') { + *info = (MPMarshallInfo){ .format = MPMarshallFormatFlat }; + mpw_marshall_read_flat_info( in, info ); + } + else if (in[0] == '{') { + *info = (MPMarshallInfo){ .format = MPMarshallFormatJSON }; + mpw_marshall_read_json_info( in, info ); + } + } + + return info; +} + MPMarshalledUser *mpw_marshall_read( - char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) { + const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) { switch (inFormat) { + case MPMarshallFormatNone: + *error = (MPMarshallError){ .type = MPMarshallSuccess }; + return false; case MPMarshallFormatFlat: return mpw_marshall_read_flat( in, masterPassword, error ); case MPMarshallFormatJSON: @@ -700,6 +822,9 @@ MPMarshalledUser *mpw_marshall_read( const MPMarshallFormat mpw_formatWithName( const char *formatName) { + if (!formatName || !strlen( formatName )) + return MPMarshallFormatNone; + // Lower-case to standardize it. size_t stdFormatNameSize = strlen( formatName ); char stdFormatName[stdFormatNameSize + 1]; @@ -707,6 +832,8 @@ const MPMarshallFormat mpw_formatWithName( stdFormatName[c] = (char)tolower( formatName[c] ); stdFormatName[stdFormatNameSize] = '\0'; + if (strncmp( mpw_nameForFormat( MPMarshallFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0) + return MPMarshallFormatNone; if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0) return MPMarshallFormatFlat; if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0) @@ -720,6 +847,8 @@ const char *mpw_nameForFormat( const MPMarshallFormat format) { switch (format) { + case MPMarshallFormatNone: + return "none"; case MPMarshallFormatFlat: return "flat"; case MPMarshallFormatJSON: @@ -735,6 +864,8 @@ const char *mpw_marshall_format_extension( const MPMarshallFormat format) { switch (format) { + case MPMarshallFormatNone: + return NULL; case MPMarshallFormatFlat: return "mpsites"; case MPMarshallFormatJSON: diff --git a/core/c/mpw-marshall.h b/core/c/mpw-marshall.h index 96b8a4e3..c6233c84 100644 --- a/core/c/mpw-marshall.h +++ b/core/c/mpw-marshall.h @@ -26,6 +26,8 @@ //// Types. typedef enum( unsigned int, MPMarshallFormat ) { + /** Generate a key for authentication. */ + MPMarshallFormatNone, /** Generate a key for authentication. */ MPMarshallFormatFlat, /** Generate a name for identification. */ @@ -91,22 +93,42 @@ typedef struct MPMarshalledUser { MPMarshalledSite *sites; } MPMarshalledUser; +typedef struct MPMarshallInfo { + MPMarshallFormat format; + MPAlgorithmVersion algorithm; + const char *fullName; + const char *keyID; + bool redacted; + time_t date; +} MPMarshallInfo; + //// Marshalling. +/** Write the user and all associated data out to the given output buffer using the given marshalling format. */ bool mpw_marshall_write( char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error); +/** Try to read metadata on the sites in the input buffer. */ +MPMarshallInfo *mpw_marshall_read_info( + const char *in); +/** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */ MPMarshalledUser *mpw_marshall_read( - char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error); + const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error); //// Utilities. +/** Create a new user object ready for marshalling. */ MPMarshalledUser *mpw_marshall_user( const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion); +/** Create a new site attached to the given user object, ready for marshalling. */ MPMarshalledSite *mpw_marshall_site( MPMarshalledUser *user, const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion); +/** Create a new question attached to the given site object, ready for marshalling. */ MPMarshalledQuestion *mpw_marshal_question( MPMarshalledSite *site, const char *keyword); +/** Free the given user object and all associated data. */ +bool mpw_marshal_info_free( + MPMarshallInfo *info); bool mpw_marshal_free( MPMarshalledUser *user); @@ -122,6 +144,9 @@ const MPMarshallFormat mpw_formatWithName( */ const char *mpw_nameForFormat( const MPMarshallFormat format); +/** + * @return The file extension that's recommended for files that use the given marshalling format. + */ const char *mpw_marshall_format_extension( const MPMarshallFormat format); diff --git a/core/c/mpw-types.c b/core/c/mpw-types.c index e8348b15..7d253c22 100644 --- a/core/c/mpw-types.c +++ b/core/c/mpw-types.c @@ -48,9 +48,9 @@ const MPResultType mpw_typeWithName(const char *typeName) { if ('n' == typeName[0]) return MPResultTypeTemplateName; if ('P' == typeName[0]) - return MPResultTypeStatePersonal; + return MPResultTypeStatefulPersonal; if ('D' == typeName[0]) - return MPResultTypeStateDevice; + return MPResultTypeStatefulDevice; if ('k' == typeName[0]) return MPResultTypeDeriveKey; } @@ -82,10 +82,10 @@ const MPResultType mpw_typeWithName(const char *typeName) { return MPResultTypeTemplateName; if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0) return MPResultTypeTemplatePhrase; - if (strncmp( mpw_nameForType( MPResultTypeStatePersonal ), stdTypeName, strlen( stdTypeName ) ) == 0) - return MPResultTypeStatePersonal; - if (strncmp( mpw_nameForType( MPResultTypeStateDevice ), stdTypeName, strlen( stdTypeName ) ) == 0) - return MPResultTypeStateDevice; + if (strncmp( mpw_nameForType( MPResultTypeStatefulPersonal ), stdTypeName, strlen( stdTypeName ) ) == 0) + return MPResultTypeStatefulPersonal; + if (strncmp( mpw_nameForType( MPResultTypeStatefulDevice ), stdTypeName, strlen( stdTypeName ) ) == 0) + return MPResultTypeStatefulDevice; if (strncmp( mpw_nameForType( MPResultTypeDeriveKey ), stdTypeName, strlen( stdTypeName ) ) == 0) return MPResultTypeDeriveKey; @@ -112,9 +112,9 @@ const char *mpw_nameForType(MPResultType resultType) { return "name"; case MPResultTypeTemplatePhrase: return "phrase"; - case MPResultTypeStatePersonal: + case MPResultTypeStatefulPersonal: return "personal"; - case MPResultTypeStateDevice: + case MPResultTypeStatefulDevice: return "device"; case MPResultTypeDeriveKey: return "key"; diff --git a/core/c/mpw-types.h b/core/c/mpw-types.h index 0a261d3e..3bfe7238 100644 --- a/core/c/mpw-types.h +++ b/core/c/mpw-types.h @@ -51,7 +51,7 @@ typedef enum( uint16_t, MPResultTypeClass ) { /** Use the site key to generate a password from a template. */ MPResultTypeClassTemplate = 1 << 4, /** Use the site key to encrypt and decrypt a stateful entity. */ - MPResultTypeClassState = 1 << 5, + MPResultTypeClassStateful = 1 << 5, /** Use the site key to derive a site-specific object. */ MPResultTypeClassDerive = 1 << 6, }; @@ -86,9 +86,9 @@ typedef enum( uint32_t, MPResultType ) { MPResultTypeTemplatePhrase = 0xF | MPResultTypeClassTemplate | 0x0, /** Custom saved password. */ - MPResultTypeStatePersonal = 0x0 | MPResultTypeClassState | MPSiteFeatureExportContent, + MPResultTypeStatefulPersonal = 0x0 | MPResultTypeClassStateful | MPSiteFeatureExportContent, /** Custom saved password that should not be exported from the device. */ - MPResultTypeStateDevice = 0x1 | MPResultTypeClassState | MPSiteFeatureDevicePrivate, + MPResultTypeStatefulDevice = 0x1 | MPResultTypeClassStateful | MPSiteFeatureDevicePrivate, /** Derive a unique binary key. */ MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative, diff --git a/core/c/mpw-util.c b/core/c/mpw-util.c index a55d1507..f2e2e4aa 100644 --- a/core/c/mpw-util.c +++ b/core/c/mpw-util.c @@ -35,7 +35,9 @@ #include "mpw-util.h" +#ifdef inf_level int mpw_verbosity = inf_level; +#endif bool mpw_push_buf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) { diff --git a/platform-darwin/MasterPassword-iOS.xcodeproj/project.pbxproj b/platform-darwin/MasterPassword-iOS.xcodeproj/project.pbxproj index e1dc6955..25c22931 100644 --- a/platform-darwin/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/platform-darwin/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -253,6 +253,7 @@ DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */; }; DAB7AE5D1F3D752900C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE5C1F3D752900C856B1 /* libjson-c.a */; }; DAB7AE771F3D755B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE761F3D755B00C856B1 /* libjson-c.a */; }; + DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */; }; DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; }; DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; }; DABD39381711E29700CF925C /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366D1711E29400CF925C /* avatar-0@2x.png */; }; @@ -903,6 +904,8 @@ DAB7AE731F3D755B00C856B1 /* strdup_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strdup_compat.h; sourceTree = ""; }; DAB7AE741F3D755B00C856B1 /* vasprintf_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vasprintf_compat.h; sourceTree = ""; }; DAB7AE761F3D755B00C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libjson-c.a"; sourceTree = ""; }; + DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall-util.h"; sourceTree = ""; }; + DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall-util.c"; sourceTree = ""; }; DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; DABD360F1711E29400CF925C /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = ""; }; DABD36101711E29400CF925C /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = ""; }; @@ -1773,6 +1776,8 @@ 93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */, 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */, 93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */, + DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */, + DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */, DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */, DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */, 93D392C5A6572DB0EB5B82C8 /* mpw-types.c */, @@ -4030,6 +4035,7 @@ DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */, 93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */, 93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */, + DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */, DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */, 93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */, DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */, @@ -4327,6 +4333,8 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", + "\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"", + "\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"", "$(inherited)", ); IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -4510,6 +4518,8 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", + "\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"", + "\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"", "$(inherited)", ); IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -4598,6 +4608,8 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", + "\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"", + "\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"", "$(inherited)", ); IPHONEOS_DEPLOYMENT_TARGET = 8.0; diff --git a/platform-darwin/MasterPassword-macOS.xcodeproj/project.pbxproj b/platform-darwin/MasterPassword-macOS.xcodeproj/project.pbxproj index 0fec6982..da7229a9 100644 --- a/platform-darwin/MasterPassword-macOS.xcodeproj/project.pbxproj +++ b/platform-darwin/MasterPassword-macOS.xcodeproj/project.pbxproj @@ -2979,8 +2979,9 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", + "\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"", + "\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"", "$(inherited)", - "$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include", ); LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; @@ -3315,8 +3316,9 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", + "\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"", + "\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"", "$(inherited)", - "$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include", ); LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; @@ -3407,8 +3409,9 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", + "\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"", + "\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"", "$(inherited)", - "$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include", ); LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; diff --git a/platform-darwin/Scripts/build_libjson-c-ios b/platform-darwin/Scripts/build_libjson-c-ios index 90dc09ad..fd3829db 100755 --- a/platform-darwin/Scripts/build_libjson-c-ios +++ b/platform-darwin/Scripts/build_libjson-c-ios @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -e -echo "ARGS: $*" cd "${BASH_SOURCE%/*}/../External/libjson-c" [[ -d libjson-c-ios ]] && exit diff --git a/platform-darwin/Scripts/build_libjson-c-osx b/platform-darwin/Scripts/build_libjson-c-osx index d265b82c..09e00f8b 100755 --- a/platform-darwin/Scripts/build_libjson-c-osx +++ b/platform-darwin/Scripts/build_libjson-c-osx @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -e -echo "ARGS: $*" cd "${BASH_SOURCE%/*}/../External/libjson-c" [[ -d libjson-c-osx ]] && exit diff --git a/platform-darwin/Scripts/build_libsodium-ios b/platform-darwin/Scripts/build_libsodium-ios index 9d35c242..39228441 100755 --- a/platform-darwin/Scripts/build_libsodium-ios +++ b/platform-darwin/Scripts/build_libsodium-ios @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -e -echo "ARGS: $*" cd "${BASH_SOURCE%/*}/../External/libsodium" [[ -d libsodium-ios ]] && exit diff --git a/platform-darwin/Scripts/build_libsodium-osx b/platform-darwin/Scripts/build_libsodium-osx index c2c99af5..86a61e3d 100755 --- a/platform-darwin/Scripts/build_libsodium-osx +++ b/platform-darwin/Scripts/build_libsodium-osx @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -e -echo "ARGS: $*" cd "${BASH_SOURCE%/*}/../External/libsodium" [[ -d libsodium-osx ]] && exit diff --git a/platform-darwin/Source/MPAlgorithm.h b/platform-darwin/Source/MPAlgorithm.h index cfb1e258..b0f35d87 100644 --- a/platform-darwin/Source/MPAlgorithm.h +++ b/platform-darwin/Source/MPAlgorithm.h @@ -52,49 +52,44 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack); - (NSData *)keyIDForKey:(MPMasterKey)masterKey; - (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword; -- (NSString *)nameOfType:(MPSiteType)type; -- (NSString *)shortNameOfType:(MPSiteType)type; -- (NSString *)classNameOfType:(MPSiteType)type; -- (Class)classOfType:(MPSiteType)type; +- (NSString *)nameOfType:(MPResultType)type; +- (NSString *)shortNameOfType:(MPResultType)type; +- (NSString *)classNameOfType:(MPResultType)type; +- (Class)classOfType:(MPResultType)type; - (NSArray *)allTypes; -- (NSArray *)allTypesStartingWith:(MPSiteType)startingType; -- (MPSiteType)defaultType; -- (MPSiteType)nextType:(MPSiteType)type; -- (MPSiteType)previousType:(MPSiteType)type; +- (NSArray *)allTypesStartingWith:(MPResultType)startingType; +- (MPResultType)defaultType; +- (MPResultType)nextType:(MPResultType)type; +- (MPResultType)previousType:(MPResultType)type; -- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key; -- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - usingKey:(MPKey *)key; -- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key; -- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key; +- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key; +- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type + withCounter:(NSUInteger)counter usingKey:(MPKey *)key; +- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key; +- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter + withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key; -- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key; -- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key; +- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)key; -- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; +- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key; +- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key; +- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key; +- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key; -- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; -- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; -- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; -- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey; - -- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey +- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock; -- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey +- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock; -- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey +- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock; -- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey +- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock; -- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey - intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; -- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site - usingKey:(MPKey *)siteKey; -- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; +- (void)importPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey + intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key; +- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key; -- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker; +- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker; - (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker; @end diff --git a/platform-darwin/Source/MPAlgorithmV0.m b/platform-darwin/Source/MPAlgorithmV0.m index fda2c012..b4bd6858 100644 --- a/platform-darwin/Source/MPAlgorithmV0.m +++ b/platform-darwin/Source/MPAlgorithmV0.m @@ -145,125 +145,125 @@ static NSOperationQueue *_mpwQueue = nil; return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256]; } -- (NSString *)nameOfType:(MPSiteType)type { +- (NSString *)nameOfType:(MPResultType)type { if (!type) return nil; switch (type) { - case MPSiteTypeGeneratedMaximum: + case MPResultTypeTemplateMaximum: return @"Maximum Security Password"; - case MPSiteTypeGeneratedLong: + case MPResultTypeTemplateLong: return @"Long Password"; - case MPSiteTypeGeneratedMedium: + case MPResultTypeTemplateMedium: return @"Medium Password"; - case MPSiteTypeGeneratedBasic: + case MPResultTypeTemplateBasic: return @"Basic Password"; - case MPSiteTypeGeneratedShort: + case MPResultTypeTemplateShort: return @"Short Password"; - case MPSiteTypeGeneratedPIN: + case MPResultTypeTemplatePIN: return @"PIN"; - case MPSiteTypeGeneratedName: + case MPResultTypeTemplateName: return @"Name"; - case MPSiteTypeGeneratedPhrase: + case MPResultTypeTemplatePhrase: return @"Phrase"; - case MPSiteTypeStoredPersonal: + case MPResultTypeStatefulPersonal: return @"Personal Password"; - case MPSiteTypeStoredDevicePrivate: + case MPResultTypeStatefulDevice: return @"Device Private Password"; } Throw( @"Type not supported: %lu", (long)type ); } -- (NSString *)shortNameOfType:(MPSiteType)type { +- (NSString *)shortNameOfType:(MPResultType)type { if (!type) return nil; switch (type) { - case MPSiteTypeGeneratedMaximum: + case MPResultTypeTemplateMaximum: return @"Maximum"; - case MPSiteTypeGeneratedLong: + case MPResultTypeTemplateLong: return @"Long"; - case MPSiteTypeGeneratedMedium: + case MPResultTypeTemplateMedium: return @"Medium"; - case MPSiteTypeGeneratedBasic: + case MPResultTypeTemplateBasic: return @"Basic"; - case MPSiteTypeGeneratedShort: + case MPResultTypeTemplateShort: return @"Short"; - case MPSiteTypeGeneratedPIN: + case MPResultTypeTemplatePIN: return @"PIN"; - case MPSiteTypeGeneratedName: + case MPResultTypeTemplateName: return @"Name"; - case MPSiteTypeGeneratedPhrase: + case MPResultTypeTemplatePhrase: return @"Phrase"; - case MPSiteTypeStoredPersonal: + case MPResultTypeStatefulPersonal: return @"Personal"; - case MPSiteTypeStoredDevicePrivate: + case MPResultTypeStatefulDevice: return @"Device"; } Throw( @"Type not supported: %lu", (long)type ); } -- (NSString *)classNameOfType:(MPSiteType)type { +- (NSString *)classNameOfType:(MPResultType)type { return NSStringFromClass( [self classOfType:type] ); } -- (Class)classOfType:(MPSiteType)type { +- (Class)classOfType:(MPResultType)type { if (!type) Throw( @"No type given." ); switch (type) { - case MPSiteTypeGeneratedMaximum: + case MPResultTypeTemplateMaximum: return [MPGeneratedSiteEntity class]; - case MPSiteTypeGeneratedLong: + case MPResultTypeTemplateLong: return [MPGeneratedSiteEntity class]; - case MPSiteTypeGeneratedMedium: + case MPResultTypeTemplateMedium: return [MPGeneratedSiteEntity class]; - case MPSiteTypeGeneratedBasic: + case MPResultTypeTemplateBasic: return [MPGeneratedSiteEntity class]; - case MPSiteTypeGeneratedShort: + case MPResultTypeTemplateShort: return [MPGeneratedSiteEntity class]; - case MPSiteTypeGeneratedPIN: + case MPResultTypeTemplatePIN: return [MPGeneratedSiteEntity class]; - case MPSiteTypeGeneratedName: + case MPResultTypeTemplateName: return [MPGeneratedSiteEntity class]; - case MPSiteTypeGeneratedPhrase: + case MPResultTypeTemplatePhrase: return [MPGeneratedSiteEntity class]; - case MPSiteTypeStoredPersonal: + case MPResultTypeStatefulPersonal: return [MPStoredSiteEntity class]; - case MPSiteTypeStoredDevicePrivate: + case MPResultTypeStatefulDevice: return [MPStoredSiteEntity class]; } @@ -272,13 +272,13 @@ static NSOperationQueue *_mpwQueue = nil; - (NSArray *)allTypes { - return [self allTypesStartingWith:MPSiteTypeGeneratedPhrase]; + return [self allTypesStartingWith:MPResultTypeTemplatePhrase]; } -- (NSArray *)allTypesStartingWith:(MPSiteType)startingType { +- (NSArray *)allTypesStartingWith:(MPResultType)startingType { NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8]; - MPSiteType currentType = startingType; + MPResultType currentType = startingType; do { [allTypes addObject:@(currentType)]; } while ((currentType = [self nextType:currentType]) != startingType); @@ -286,199 +286,170 @@ static NSOperationQueue *_mpwQueue = nil; return allTypes; } -- (MPSiteType)defaultType { +- (MPResultType)defaultType { - return MPSiteTypeGeneratedLong; + return MPResultTypeTemplateLong; } -- (MPSiteType)nextType:(MPSiteType)type { +- (MPResultType)nextType:(MPResultType)type { switch (type) { - case MPSiteTypeGeneratedPhrase: - return MPSiteTypeGeneratedName; - case MPSiteTypeGeneratedName: - return MPSiteTypeGeneratedMaximum; - case MPSiteTypeGeneratedMaximum: - return MPSiteTypeGeneratedLong; - case MPSiteTypeGeneratedLong: - return MPSiteTypeGeneratedMedium; - case MPSiteTypeGeneratedMedium: - return MPSiteTypeGeneratedBasic; - case MPSiteTypeGeneratedBasic: - return MPSiteTypeGeneratedShort; - case MPSiteTypeGeneratedShort: - return MPSiteTypeGeneratedPIN; - case MPSiteTypeGeneratedPIN: - return MPSiteTypeStoredPersonal; - case MPSiteTypeStoredPersonal: - return MPSiteTypeStoredDevicePrivate; - case MPSiteTypeStoredDevicePrivate: - return MPSiteTypeGeneratedPhrase; + case MPResultTypeTemplatePhrase: + return MPResultTypeTemplateName; + case MPResultTypeTemplateName: + return MPResultTypeTemplateMaximum; + case MPResultTypeTemplateMaximum: + return MPResultTypeTemplateLong; + case MPResultTypeTemplateLong: + return MPResultTypeTemplateMedium; + case MPResultTypeTemplateMedium: + return MPResultTypeTemplateBasic; + case MPResultTypeTemplateBasic: + return MPResultTypeTemplateShort; + case MPResultTypeTemplateShort: + return MPResultTypeTemplatePIN; + case MPResultTypeTemplatePIN: + return MPResultTypeStatefulPersonal; + case MPResultTypeStatefulPersonal: + return MPResultTypeStatefulDevice; + case MPResultTypeStatefulDevice: + return MPResultTypeTemplatePhrase; } return [self defaultType]; } -- (MPSiteType)previousType:(MPSiteType)type { +- (MPResultType)previousType:(MPResultType)type { - MPSiteType previousType = type, nextType = type; + MPResultType previousType = type, nextType = type; while ((nextType = [self nextType:nextType]) != type) previousType = nextType; return previousType; } -- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key { +- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key { - return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1 - variant:MPKeyPurposeIdentification context:nil usingKey:key]; + return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplateName parameter:nil withCounter:1 + variant:MPKeyPurposeIdentification context:nil usingKey:key]; } -- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - usingKey:(MPKey *)key { +- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type + withCounter:(NSUInteger)counter usingKey:(MPKey *)key { - return [self generateContentForSiteNamed:name ofType:type withCounter:counter - variant:MPKeyPurposeAuthentication context:nil usingKey:key]; + return [self mpwResultForSiteNamed:name ofType:type parameter:nil withCounter:counter + variant:MPKeyPurposeAuthentication context:nil usingKey:key]; } -- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key { +- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key { - return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1 - variant:MPKeyPurposeRecovery context:question usingKey:key]; + return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplatePhrase parameter:nil withCounter:1 + variant:MPKeyPurposeRecovery context:question usingKey:key]; } -- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { +- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter + withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key { - __block NSString *content = nil; + __block NSString *result = nil; [self mpw_perform:^{ - char const *contentBytes = mpw_passwordForSite( [key keyForAlgorithm:self], - name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] ); - if (contentBytes) { - content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding]; - mpw_free_string( contentBytes ); + char const *resultBytes = mpw_siteResult( [key keyForAlgorithm:self], + name.UTF8String, (uint32_t)counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] ); + if (resultBytes) { + result = [NSString stringWithCString:resultBytes encoding:NSUTF8StringEncoding]; + mpw_free_string( resultBytes ); } }]; - return content; + return result; } -- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key { +- (BOOL)savePassword:(NSString *)plainText toSite:(MPSiteEntity *)site usingKey:(MPKey *)key { - return nil; -} - -- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key { - - return [self decryptContent:site.contentObject usingKey:key]; -} - -- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - - NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); - switch (site.type) { - case MPSiteTypeGeneratedMaximum: - case MPSiteTypeGeneratedLong: - case MPSiteTypeGeneratedMedium: - case MPSiteTypeGeneratedBasic: - case MPSiteTypeGeneratedShort: - case MPSiteTypeGeneratedPIN: - case MPSiteTypeGeneratedName: - case MPSiteTypeGeneratedPhrase: { - wrn( @"Cannot save content to site with generated type %lu.", (long)site.type ); - return NO; - } - - case MPSiteTypeStoredPersonal: { - if (![site isKindOfClass:[MPStoredSiteEntity class]]) { - wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", - (long)site.type, [site class] ); - return NO; - } - - NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize]; - NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] - encryptWithSymmetricKey:encryptionKey padding:YES]; - if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent]) - return NO; - - ((MPStoredSiteEntity *)site).contentObject = encryptedContent; - return YES; - } - case MPSiteTypeStoredDevicePrivate: { - if (![site isKindOfClass:[MPStoredSiteEntity class]]) { - wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", - (long)site.type, [site class] ); - return NO; - } - - NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize]; - NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] - encryptWithSymmetricKey:encryptionKey padding:YES]; - NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name]; - if (!encryptedContent) - [PearlKeyChain deleteItemForQuery:siteQuery]; - else - [PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{ - (__bridge id)kSecValueData : encryptedContent, -#if TARGET_OS_IPHONE - (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, -#endif - }]; - ((MPStoredSiteEntity *)site).contentObject = nil; - return YES; - } + if (!(site.type & MPResultTypeClassStateful)) { + wrn( @"Can only save content to site with a stateful type: %lu.", (long)site.type ); + return NO; } - Throw( @"Unsupported type: %ld", (long)site.type ); + NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + if (![site isKindOfClass:[MPStoredSiteEntity class]]) { + wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", + (long)site.type, [site class] ); + return NO; + } + + __block NSData *state = nil; + if (plainText) + [self mpw_perform:^{ + char const *stateBytes = mpw_siteState( [key keyForAlgorithm:self], site.name.UTF8String, + MPCounterValueInitial, MPKeyPurposeAuthentication, NULL, site.type, plainText.UTF8String, [self version] ); + if (stateBytes) { + state = [[NSString stringWithCString:stateBytes encoding:NSUTF8StringEncoding] decodeBase64]; + mpw_free_string( stateBytes ); + } + }]; + + NSDictionary *siteQuery = [self queryForSite:site]; + if (!state) + [PearlKeyChain deleteItemForQuery:siteQuery]; + else + [PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{ + (__bridge id)kSecValueData : state, +#if TARGET_OS_IPHONE + (__bridge id)kSecAttrAccessible: + site.type & MPSiteFeatureDevicePrivate? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly + : (__bridge id)kSecAttrAccessibleWhenUnlocked, +#endif + }]; + ((MPStoredSiteEntity *)site).contentObject = nil; + return YES; } -- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { +- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key { return PearlAwait( ^(void (^setResult)(id)) { - [self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) { + [self resolveLoginForSite:site usingKey:key result:^(NSString *result_) { setResult( result_ ); }]; } ); } -- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { +- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key { return PearlAwait( ^(void (^setResult)(id)) { - [self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) { + [self resolvePasswordForSite:site usingKey:key result:^(NSString *result_) { setResult( result_ ); }]; } ); } -- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { +- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key { return PearlAwait( ^(void (^setResult)(id)) { - [self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) { + [self resolveAnswerForSite:site usingKey:key result:^(NSString *result_) { setResult( result_ ); }]; } ); } -- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey { +- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key { return PearlAwait( ^(void (^setResult)(id)) { - [self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) { + [self resolveAnswerForQuestion:question usingKey:key result:^(NSString *result_) { setResult( result_ ); }]; } ); } -- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { +- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock { - NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); NSString *name = site.name; BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins]; NSString *loginName = site.loginName; id algorithm = nil; if (!name.length) err( @"Missing name." ); - else if (!siteKey) + else if (!key) err( @"Missing key." ); else algorithm = site.algorithm; @@ -487,244 +458,139 @@ static NSOperationQueue *_mpwQueue = nil; resultBlock( loginName ); else PearlNotMainQueue( ^{ - resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] ); + resultBlock( [algorithm mpwLoginForSiteNamed:name usingKey:key] ); } ); } -- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { +- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock { + + NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSString *name = site.name; + MPResultType type = site.type; + id algorithm = nil; + if (!site.name.length) + err( @"Missing name." ); + else if (!key) + err( @"Missing key." ); + else + algorithm = site.algorithm; - NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); switch (site.type) { - case MPSiteTypeGeneratedMaximum: - case MPSiteTypeGeneratedLong: - case MPSiteTypeGeneratedMedium: - case MPSiteTypeGeneratedBasic: - case MPSiteTypeGeneratedShort: - case MPSiteTypeGeneratedPIN: - case MPSiteTypeGeneratedName: - case MPSiteTypeGeneratedPhrase: { + case MPResultTypeTemplateMaximum: + case MPResultTypeTemplateLong: + case MPResultTypeTemplateMedium: + case MPResultTypeTemplateBasic: + case MPResultTypeTemplateShort: + case MPResultTypeTemplatePIN: + case MPResultTypeTemplateName: + case MPResultTypeTemplatePhrase: { if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) { wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.", (long)site.type, [site class] ); break; } - NSString *name = site.name; - MPSiteType type = site.type; NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter; - id algorithm = nil; - if (!site.name.length) - err( @"Missing name." ); - else if (!siteKey) - err( @"Missing key." ); - else - algorithm = site.algorithm; PearlNotMainQueue( ^{ - resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] ); + resultBlock( [algorithm mpwTemplateForSiteNamed:name ofType:type withCounter:counter usingKey:key] ); } ); break; } - case MPSiteTypeStoredPersonal: { + case MPResultTypeStatefulPersonal: + case MPResultTypeStatefulDevice: { if (![site isKindOfClass:[MPStoredSiteEntity class]]) { wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type, [site class] ); break; } - NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject; + NSDictionary *siteQuery = [self queryForSite:site]; + NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery]; + state = state?: ((MPStoredSiteEntity *)site).contentObject; PearlNotMainQueue( ^{ - resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] ); - } ); - break; - } - case MPSiteTypeStoredDevicePrivate: { - NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]], - @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type, - [site class] ); - - NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name]; - NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery]; - - PearlNotMainQueue( ^{ - resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] ); + resultBlock( [algorithm mpwResultForSiteNamed:name ofType:type parameter:[state encodeBase64] + withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil + usingKey:key] ); } ); break; } } } -- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { +- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock { - NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); NSString *name = site.name; id algorithm = nil; if (!site.name.length) err( @"Missing name." ); - else if (!siteKey) + else if (!key) err( @"Missing key." ); else algorithm = site.algorithm; PearlNotMainQueue( ^{ - resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] ); + resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:nil usingKey:key] ); } ); } -- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey +- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock { - NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID], + NSAssert( [[key keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID], @"Site does not belong to current user." ); NSString *name = question.site.name; NSString *keyword = question.keyword; id algorithm = nil; if (!name.length) err( @"Missing name." ); - else if (!siteKey) + else if (!key) err( @"Missing key." ); else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers]) algorithm = question.site.algorithm; PearlNotMainQueue( ^{ - resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] ); + resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:keyword usingKey:key] ); } ); } -- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey - intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { +- (void)importPassword:(NSString *)cipherText protectedByKey:(MPKey *)importKey + intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key { - NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); - switch (site.type) { - case MPSiteTypeGeneratedMaximum: - case MPSiteTypeGeneratedLong: - case MPSiteTypeGeneratedMedium: - case MPSiteTypeGeneratedBasic: - case MPSiteTypeGeneratedShort: - case MPSiteTypeGeneratedPIN: - case MPSiteTypeGeneratedName: - case MPSiteTypeGeneratedPhrase: - break; - - case MPSiteTypeStoredPersonal: { - if (![site isKindOfClass:[MPStoredSiteEntity class]]) { - wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", - (long)site.type, [site class] ); - break; - } - if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]]) - ((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64]; - - else { - NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey]; - [self importClearTextPassword:clearContent intoSite:site usingKey:siteKey]; - } - break; - } - - case MPSiteTypeStoredDevicePrivate: - break; + NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + if (cipherText && cipherText.length && site.type & MPResultTypeClassStateful) { + NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:site.type parameter:cipherText + withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil + usingKey:importKey]; + if (plainText) + [self savePassword:plainText toSite:site usingKey:key]; } } -- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { +- (NSDictionary *)queryForSite:(MPSiteEntity *)site { - NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); - switch (site.type) { - case MPSiteTypeGeneratedMaximum: - case MPSiteTypeGeneratedLong: - case MPSiteTypeGeneratedMedium: - case MPSiteTypeGeneratedBasic: - case MPSiteTypeGeneratedShort: - case MPSiteTypeGeneratedPIN: - case MPSiteTypeGeneratedName: - case MPSiteTypeGeneratedPhrase: - break; - - case MPSiteTypeStoredPersonal: { - [self savePassword:clearContent toSite:site usingKey:siteKey]; - break; - } - - case MPSiteTypeStoredDevicePrivate: - break; - } + return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:@{ + (__bridge id)kSecAttrService: site.type & MPSiteFeatureDevicePrivate? @"DevicePrivate": @"Private", + (__bridge id)kSecAttrAccount: site.name + } matches:nil]; } -- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { +- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key { - NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); if (!(site.type & MPSiteFeatureExportContent)) return nil; - NSString *result = nil; - switch (site.type) { - case MPSiteTypeGeneratedMaximum: - case MPSiteTypeGeneratedLong: - case MPSiteTypeGeneratedMedium: - case MPSiteTypeGeneratedBasic: - case MPSiteTypeGeneratedShort: - case MPSiteTypeGeneratedPIN: - case MPSiteTypeGeneratedName: - case MPSiteTypeGeneratedPhrase: { - result = nil; - break; - } - - case MPSiteTypeStoredPersonal: { - if (![site isKindOfClass:[MPStoredSiteEntity class]]) { - wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", - (long)site.type, [site class] ); - break; - } - result = [((MPStoredSiteEntity *)site).contentObject encodeBase64]; - break; - } - - case MPSiteTypeStoredDevicePrivate: { - result = nil; - break; - } - } - - return result; + NSDictionary *siteQuery = [self queryForSite:site]; + NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery]; + return [state?: ((MPStoredSiteEntity *)site).contentObject encodeBase64]; } -- (BOOL)migrateExplicitly:(BOOL)explicit { +- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker { - return NO; -} - -- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name { - - return [PearlKeyChain createQueryForClass:kSecClassGenericPassword - attributes:@{ - (__bridge id)kSecAttrService: @"DevicePrivate", - (__bridge id)kSecAttrAccount: name - } - matches:nil]; -} - -- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key { - - if (!key) - return nil; - NSData *decryptedContent = nil; - if ([encryptedContent length]) { - NSData *encryptionKey = [key keyForAlgorithm:self trimmedLength:PearlCryptKeySize]; - decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES]; - } - if (!decryptedContent) - return nil; - - return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; -} - -- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker { - - if (!(type & MPSiteTypeClassGenerated)) + if (!(type & MPResultTypeClassTemplate)) return NO; size_t count = 0; const char **templates = mpw_templatesForType( type, &count ); diff --git a/platform-darwin/Source/MPAlgorithmV1.m b/platform-darwin/Source/MPAlgorithmV1.m index 5c93f72e..d1f341ca 100644 --- a/platform-darwin/Source/MPAlgorithmV1.m +++ b/platform-darwin/Source/MPAlgorithmV1.m @@ -33,7 +33,7 @@ return NO; if (!explicit) { - if (site.type & MPSiteTypeClassGenerated) { + if (site.type & MPResultTypeClassTemplate) { // This migration requires explicit permission for types of the generated class. site.requiresExplicitMigration = YES; return NO; diff --git a/platform-darwin/Source/MPAlgorithmV2.m b/platform-darwin/Source/MPAlgorithmV2.m index 4828ff0e..d9b7ff67 100644 --- a/platform-darwin/Source/MPAlgorithmV2.m +++ b/platform-darwin/Source/MPAlgorithmV2.m @@ -33,7 +33,7 @@ return NO; if (!explicit) { - if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) { + if (site.type & MPResultTypeClassTemplate && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) { // This migration requires explicit permission for types of the generated class. site.requiresExplicitMigration = YES; return NO; diff --git a/platform-darwin/Source/MPAlgorithmV3.m b/platform-darwin/Source/MPAlgorithmV3.m index f304939e..f8feea93 100644 --- a/platform-darwin/Source/MPAlgorithmV3.m +++ b/platform-darwin/Source/MPAlgorithmV3.m @@ -33,7 +33,7 @@ return NO; if (!explicit) { - if (site.type & MPSiteTypeClassGenerated && + if (site.type & MPResultTypeClassTemplate && site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) { // This migration requires explicit permission for types of the generated class. site.requiresExplicitMigration = YES; diff --git a/platform-darwin/Source/MPAppDelegate_Key.m b/platform-darwin/Source/MPAppDelegate_Key.m index 78c3ce42..71800e99 100644 --- a/platform-darwin/Source/MPAppDelegate_Key.m +++ b/platform-darwin/Source/MPAppDelegate_Key.m @@ -248,9 +248,9 @@ #endif for (MPSiteEntity *site in user.sites) { - if (site.type & MPSiteTypeClassStored) { + if (site.type & MPResultTypeClassStateful) { NSString *content; - while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) { + while (!(content = [site.algorithm resolvePasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) { // Failed to decrypt site with the current recoveryKey. Ask user for a new one to use. NSString *masterPassword = nil; diff --git a/platform-darwin/Source/MPAppDelegate_Store.h b/platform-darwin/Source/MPAppDelegate_Store.h index 836f6b55..cf1801b3 100644 --- a/platform-darwin/Source/MPAppDelegate_Store.h +++ b/platform-darwin/Source/MPAppDelegate_Store.h @@ -20,14 +20,6 @@ #import "MPFixable.h" -typedef NS_ENUM( NSUInteger, MPImportResult ) { - MPImportResultSuccess, - MPImportResultCancelled, - MPImportResultInvalidPassword, - MPImportResultMalformedInput, - MPImportResultInternalError, -}; - @interface MPAppDelegate_Shared(Store) + (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady; @@ -42,10 +34,13 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) { /** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */ - (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion; -- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type; -- (MPImportResult)importSites:(NSString *)importedSitesString +- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type; +- (void)importSites:(NSString *)importData askImportPassword:(NSString *( ^ )(NSString *userName))importPassword - askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword; -- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords; + askUserPassword:(NSString *( ^ )(NSString *userName))userPassword + result:(void ( ^ )(NSError *error))resultBlock; +- (void)exportSitesRevealPasswords:(BOOL)revealPasswords + askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword + result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock; @end diff --git a/platform-darwin/Source/MPAppDelegate_Store.m b/platform-darwin/Source/MPAppDelegate_Store.m index 58d5bd88..d7a66c18 100644 --- a/platform-darwin/Source/MPAppDelegate_Store.m +++ b/platform-darwin/Source/MPAppDelegate_Store.m @@ -18,6 +18,7 @@ #import "MPAppDelegate_Store.h" #import "mpw-marshall.h" +#import "mpw-util.h" #if TARGET_OS_IPHONE #define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete, @@ -489,7 +490,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); return; } - MPSiteType type = activeUser.defaultType; + MPResultType type = activeUser.defaultType; id algorithm = MPAlgorithmDefault; Class entityType = [algorithm classOfType:type]; @@ -506,7 +507,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); }]; } -- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type { +- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type { if (site.type == type) return site; @@ -539,328 +540,214 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); return site; } -- (MPImportResult)importSites:(NSString *)importedSitesString - askImportPassword:(NSString *( ^ )(NSString *userName))importPassword - askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword { +- (void)importSites:(NSString *)importData + askImportPassword:(NSString *( ^ )(NSString *userName))importPassword + askUserPassword:(NSString *( ^ )(NSString *userName))userPassword + result:(void ( ^ )(NSError *error))resultBlock { NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." ); - __block MPImportResult result = MPImportResultCancelled; do { if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { - result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword - saveInContext:context]; + NSError *error = [self importSites:importData askImportPassword:importPassword askUserPassword:userPassword + saveInContext:context]; + PearlMainQueue( ^{ + resultBlock( error ); + } ); }]) break; usleep( (useconds_t)(USEC_PER_SEC * 0.2) ); } while (YES); - - return result; } -- (MPImportResult)importSites:(NSString *)importedSitesString - askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword - askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword - saveInContext:(NSManagedObjectContext *)context { +- (NSError *)importSites:(NSString *)importData + askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword + askUserPassword:(NSString *( ^ )(NSString *userName))askUserPassword + saveInContext:(NSManagedObjectContext *)context { - // Compile patterns. - static NSRegularExpression *headerPattern; - static NSArray *sitePatterns; - NSError *error = nil; - if (!headerPattern) { - headerPattern = [[NSRegularExpression alloc] - initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" - options:(NSRegularExpressionOptions)0 error:&error]; - if (error) { - MPError( error, @"Error loading the header pattern." ); - return MPImportResultInternalError; + // Read metadata for the import file. + MPMarshallInfo *info = mpw_marshall_read_info( importData.UTF8String ); + if (info->format == MPMarshallFormatNone) + return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{ + @"type" : @(MPMarshallErrorFormat), + NSLocalizedDescriptionKey: @"This is not a Master Password import file.", + }]), @"While importing sites." ); + + // Get master password for import file. + MPKey *importKey; + NSString *importMasterPassword; + do { + importMasterPassword = askImportPassword( @(info->fullName) ); + if (!importMasterPassword) { + inf( @"Import cancelled." ); + mpw_marshal_info_free( info ); + return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" ); } - } - if (!sitePatterns) { - sitePatterns = @[ - [[NSRegularExpression alloc] // Format 0 - initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)" - options:(NSRegularExpressionOptions)0 error:&error], - [[NSRegularExpression alloc] // Format 1 - initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)" - options:(NSRegularExpressionOptions)0 error:&error] - ]; - if (error) { - MPError( error, @"Error loading the site patterns." ); - return MPImportResultInternalError; - } - } + + importKey = [[MPKey alloc] initForFullName:@(info->fullName) withMasterPassword:importMasterPassword]; + } while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( info->algorithm )] encodeHex] + caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame); // Parse import data. - inf( @"Importing sites." ); - NSUInteger importFormat = 0; - __block MPUserEntity *user = nil; - NSUInteger importAvatar = NSNotFound; - NSData *importKeyID = nil; - NSString *importBundleVersion = nil, *importUserName = nil; - id importAlgorithm = nil; - MPSiteType importDefaultType = (MPSiteType)0; - BOOL headerStarted = NO, headerEnded = NO, clearText = NO; - NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - NSMutableSet *sitesToDelete = [NSMutableSet set]; - NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]]; - NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )]; - for (NSString *importedSiteLine in importedSiteLines) { - if ([importedSiteLine hasPrefix:@"#"]) { - // Comment or header - if (!headerStarted) { - if ([importedSiteLine isEqualToString:@"##"]) - headerStarted = YES; - continue; - } - if (headerEnded) - continue; - if ([importedSiteLine isEqualToString:@"##"]) { - headerEnded = YES; - continue; + MPMarshallError importError = { .type = MPMarshallSuccess }; + MPMarshalledUser *importUser = mpw_marshall_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError ); + mpw_marshal_info_free( info ); + + @try { + if (!importUser || importError.type != MPMarshallSuccess) + return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{ + @"type" : @(importError.type), + NSLocalizedDescriptionKey: @(importError.description), + }]), @"While importing sites." ); + + // Find an existing user to update. + NSError *error = nil; + NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; + userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", @(importUser->fullName)]; + NSArray *users = [context executeFetchRequest:userFetchRequest error:&error]; + if (!users) + return MPError( error, @"While looking for user: %@.", @(importUser->fullName) ); + if ([users count] > 1) + return MPMakeError( @"While looking for user: %@, found more than one: %zu", + @(importUser->fullName), (size_t)[users count] ); + + // Get master key for user. + MPUserEntity *user = [users lastObject]; + MPKey *userKey = importKey; + while (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) { + NSString *userMasterPassword = askUserPassword( user.name ); + if (!userMasterPassword) { + inf( @"Import cancelled." ); + return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" ); } - // Header - if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0 - range:NSMakeRange( 0, [importedSiteLine length] )] != 1) { - err( @"Invalid header format in line: %@", importedSiteLine ); - return MPImportResultMalformedInput; - } - NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0 - range:NSMakeRange( 0, [importedSiteLine length] )] lastObject]; - NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]]; - NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]]; - - if ([headerName isEqualToString:@"Format"]) { - importFormat = (NSUInteger)[headerValue integerValue]; - if (importFormat >= [sitePatterns count]) { - err( @"Unsupported import format: %lu", (unsigned long)importFormat ); - return MPImportResultInternalError; - } - } - if (([headerName isEqualToString:@"User Name"] || [headerName isEqualToString:@"Full Name"]) && !importUserName) { - importUserName = headerValue; - - NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; - userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName]; - NSArray *users = [context executeFetchRequest:userFetchRequest error:&error]; - if (!users) { - MPError( error, @"While looking for user: %@.", importUserName ); - return MPImportResultInternalError; - } - if ([users count] > 1) { - err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] ); - return MPImportResultInternalError; - } - - user = [users lastObject]; - dbg( @"Existing user? %@", [user debugDescription] ); - } - if ([headerName isEqualToString:@"Avatar"]) - importAvatar = (NSUInteger)[headerValue integerValue]; - if ([headerName isEqualToString:@"Key ID"]) - importKeyID = [headerValue decodeHex]; - if ([headerName isEqualToString:@"Version"]) { - importBundleVersion = headerValue; - importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion ); - } - if ([headerName isEqualToString:@"Algorithm"]) - importAlgorithm = MPAlgorithmForVersion( (MPAlgorithmVersion)[headerValue integerValue] ); - if ([headerName isEqualToString:@"Default Type"]) - importDefaultType = (MPSiteType)[headerValue integerValue]; - if ([headerName isEqualToString:@"Passwords"]) { - if ([headerValue isEqualToString:@"VISIBLE"]) - clearText = YES; - } - - continue; - } - if (!headerEnded) - continue; - if (![importUserName length]) - return MPImportResultMalformedInput; - if (![importedSiteLine length]) - continue; - - // Site - NSRegularExpression *sitePattern = sitePatterns[importFormat]; - if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0 - range:NSMakeRange( 0, [importedSiteLine length] )] != 1) { - err( @"Invalid site format in line: %@", importedSiteLine ); - return MPImportResultMalformedInput; - } - NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0 - range:NSMakeRange( 0, [importedSiteLine length] )] lastObject]; - NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent; - switch (importFormat) { - case 0: - lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; - uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; - type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; - version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; - if ([version length]) - version = [version substringFromIndex:1]; // Strip the leading colon. - counter = @""; - loginName = @""; - siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; - exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]]; - break; - case 1: - lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; - uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; - type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; - version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; - if ([version length]) - version = [version substringFromIndex:1]; // Strip the leading colon. - counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; - if ([counter length]) - counter = [counter substringFromIndex:1]; // Strip the leading colon. - loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]]; - siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]]; - exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]]; - break; - default: - err( @"Unexpected import format: %lu", (unsigned long)importFormat ); - return MPImportResultInternalError; + userKey = [[MPKey alloc] initForFullName:@(importUser->fullName) withMasterPassword:userMasterPassword]; } - // Find existing site. - if (user) { - siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user]; - NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error]; - if (!existingSites) { - MPError( error, @"Lookup of existing sites failed for site: %@, user: %@.", siteName, user.userID ); - return MPImportResultInternalError; - } - if ([existingSites count]) { - dbg( @"Existing sites: %@", existingSites ); - [sitesToDelete addObjectsFromArray:existingSites]; - } + // Update or create user. + if (!user) { + user = [MPUserEntity insertNewObjectInContext:context]; + user.name = @(importUser->fullName); } - [importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]]; - dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@", - lastUsed, uses, type, version, counter, loginName, siteName, exportContent ); - } - - // Ask for confirmation to import these sites and the master password of the user. - inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count], - (unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] ); - NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count], - [sitesToDelete count] ); - if (!userMasterPassword) { - inf( @"Import cancelled." ); - return MPImportResultCancelled; - } - MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword]; - if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) - return MPImportResultInvalidPassword; - __block MPKey *importKey = userKey; - if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID]) - importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )]; - if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID]) - return MPImportResultInvalidPassword; - - // Delete existing sites. - if (sitesToDelete.count) - [sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { - inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] ); - [context deleteObject:obj]; - }]; - - // Make sure there is a user. - if (user) { - if (importAvatar != NSNotFound) - user.avatar = importAvatar; - if (importDefaultType) - user.defaultType = importDefaultType; - dbg( @"Updating User: %@", [user debugDescription] ); - } - else { - user = [MPUserEntity insertNewObjectInContext:context]; - user.name = importUserName; - user.algorithm = MPAlgorithmDefault; + user.algorithm = MPAlgorithmForVersion( importUser->algorithm ); user.keyID = [userKey keyIDForAlgorithm:user.algorithm]; - user.defaultType = importDefaultType?: user.algorithm.defaultType; - if (importAvatar != NSNotFound) - user.avatar = importAvatar; - dbg( @"Created User: %@", [user debugDescription] ); - } + user.avatar = importUser->avatar; + user.defaultType = importUser->defaultType; + user.lastUsed = [NSDate dateWithTimeIntervalSince1970:MAX( user.lastUsed.timeIntervalSince1970, importUser->lastUsed )]; + dbg( @"Importing user: %@", [user debugDescription] ); - // Import new sites. - for (NSArray *siteElements in importedSiteSites) { - NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]]; - NSUInteger uses = (unsigned)[siteElements[1] integerValue]; - MPSiteType type = (MPSiteType)[siteElements[2] integerValue]; - MPAlgorithmVersion version = (MPAlgorithmVersion)[siteElements[3] integerValue]; - NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound; - NSString *loginName = [siteElements[5] length]? siteElements[5]: nil; - NSString *siteName = siteElements[6]; - NSString *exportContent = siteElements[7]; + // Update or create sites. + for (size_t s = 0; s < importUser->sites_count; ++s) { + MPMarshalledSite *importSite = &importUser->sites[s]; - // Create new site. - id algorithm = MPAlgorithmForVersion( version ); - Class entityType = [algorithm classOfType:type]; - if (!entityType) { - err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type ); - return MPImportResultInternalError; + // Find an existing site to update. + NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )]; + siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", @(importSite->name), user]; + NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error]; + if (!existingSites) + return MPError( error, @"Lookup of existing sites failed for site: %@, user: %@", @(importSite->name), user.userID ); + if ([existingSites count]) + // Update existing site. + for (MPSiteEntity *site in existingSites) { + [self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey]; + dbg( @"Updated site: %@", [site debugDescription] ); + } + else { + // Create new site. + id algorithm = MPAlgorithmForVersion( importSite->algorithm ); + Class entityType = [algorithm classOfType:importSite->type]; + if (!entityType) + return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->name), (long)importSite->type ); + + MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context]; + site.user = user; + + [self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey]; + dbg( @"Created site: %@", [site debugDescription] ); + } } - MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context]; - site.name = siteName; - site.loginName = loginName; - site.user = user; - site.type = type; - site.uses = uses; - site.lastUsed = lastUsed; - site.algorithm = algorithm; - if ([exportContent length]) { - if (clearText) - [site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey]; - else - [site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey]; - } - if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound) - ((MPGeneratedSiteEntity *)site).counter = counter; - dbg( @"Created Site: %@", [site debugDescription] ); + if (![context saveToStore]) + return MPMakeError( @"Failed saving imported changes." ); + + inf( @"Import completed successfully." ); + [[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{ + MPSitesImportedNotificationUserKey: user + }]; + return nil; + } + @finally { + mpw_marshal_free( importUser ); } - - if (![context saveToStore]) - return MPImportResultInternalError; - - inf( @"Import completed successfully." ); - - [[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{ - MPSitesImportedNotificationUserKey: user - }]; - - return MPImportResultSuccess; } -- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords { +- (void)importSite:(const MPMarshalledSite *)importSite protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site + usingKey:(MPKey *)userKey { - MPUserEntity *activeUser = [self activeUserForMainThread]; - inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID ); + site.name = @(importSite->name); + if (importSite->content) + [site.algorithm importPassword:@(importSite->content) protectedByKey:importKey intoSite:site usingKey:userKey]; + site.type = importSite->type; + if ([site isKindOfClass:[MPGeneratedSiteEntity class]]) + ((MPGeneratedSiteEntity *)site).counter = importSite->counter; + site.algorithm = MPAlgorithmForVersion( importSite->algorithm ); + site.loginName = importSite->loginName? @(importSite->loginName): nil; + site.loginGenerated = importSite->loginGenerated; + site.url = importSite->url? @(importSite->url): nil; + site.uses = importSite->uses; + site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed]; +} - MPMarshalledUser exportUser = mpw_marshall_user( activeUser.name.UTF8String, - [self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version ); - exportUser.avatar = activeUser.avatar; - exportUser.defaultType = activeUser.defaultType; - exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970; +- (void)exportSitesRevealPasswords:(BOOL)revealPasswords + askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword + result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock { + [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity *user = [self activeUserInContext:context]; + NSString *masterPassword = askImportPassword( user.name ); - for (MPSiteEntity *site in activeUser.sites) { - MPMarshalledSite exportSite = mpw_marshall_site( &exportUser, - site.name.UTF8String, site.type, site.counter, site.algorithm.version ); - exportSite.loginName = site.loginName.UTF8String; - exportSite.url = site.url.UTF8String; - exportSite.uses = site.uses; - exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970; + inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID ); + MPMarshalledUser *exportUser = mpw_marshall_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version ); + exportUser->redacted = !revealPasswords; + exportUser->avatar = (unsigned int)user.avatar; + exportUser->defaultType = user.defaultType; + exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970; - for (MPSiteQuestionEntity *siteQuestion in site.questions) - mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String ); - } + for (MPSiteEntity *site in user.sites) { + MPCounterValue counter = MPCounterValueInitial; + if ([site isKindOfClass:[MPGeneratedSiteEntity class]]) + counter = ((MPGeneratedSiteEntity *)site).counter; + NSString *content = revealPasswords + ? [site.algorithm exportPasswordForSite:site usingKey:self.key] + : [site.algorithm resolvePasswordForSite:site usingKey:self.key]; - mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser ); + MPMarshalledSite *exportSite = mpw_marshall_site( exportUser, + site.name.UTF8String, site.type, counter, site.algorithm.version ); + exportSite->content = content.UTF8String; + exportSite->loginName = site.loginName.UTF8String; + exportSite->loginGenerated = site.loginGenerated; + exportSite->url = site.url.UTF8String; + exportSite->uses = (unsigned int)site.uses; + exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970; + + for (MPSiteQuestionEntity *siteQuestion in site.questions) + mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String ); + } + + char *export = NULL; + MPMarshallError exportError = (MPMarshallError){ .type= MPMarshallSuccess }; + mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser, &exportError ); + NSString *mpsites = nil; + if (export && exportError.type == MPMarshallSuccess) + mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding]; + mpw_free_string( export ); + + resultBlock( mpsites, exportError.type == MPMarshallSuccess? nil: + [NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{ + @"type" : @(exportError.type), + NSLocalizedDescriptionKey: @(exportError.description), + }] ); + }]; } @end diff --git a/platform-darwin/Source/MPEntities.h b/platform-darwin/Source/MPEntities.h index a5eba703..6d17ef41 100644 --- a/platform-darwin/Source/MPEntities.h +++ b/platform-darwin/Source/MPEntities.h @@ -48,7 +48,7 @@ @interface MPSiteEntity(MP) @property(assign) BOOL loginGenerated; -@property(assign) MPSiteType type; +@property(assign) MPResultType type; @property(readonly) NSString *typeName; @property(readonly) NSString *typeShortName; @property(readonly) NSString *typeClassName; @@ -71,7 +71,7 @@ @interface MPGeneratedSiteEntity(MP) -@property(assign) NSUInteger counter; +@property(assign) MPCounterValue counter; @end @@ -80,7 +80,7 @@ @property(assign) NSUInteger avatar; @property(assign) BOOL saveKey; @property(assign) BOOL touchID; -@property(assign) MPSiteType defaultType; +@property(assign) MPResultType defaultType; @property(readonly) NSString *userID; @property(strong) id algorithm; diff --git a/platform-darwin/Source/MPEntities.m b/platform-darwin/Source/MPEntities.m index 5a08af2d..9739a91d 100644 --- a/platform-darwin/Source/MPEntities.m +++ b/platform-darwin/Source/MPEntities.m @@ -82,9 +82,9 @@ return MPFixableResultNoProblems; } -- (MPSiteType)type { +- (MPResultType)type { - return (MPSiteType)[self.type_ unsignedIntegerValue]; + return (MPResultType)[self.type_ unsignedIntegerValue]; } - (void)setLoginGenerated:(BOOL)aLoginGenerated { @@ -97,7 +97,7 @@ return [self.loginGenerated_ boolValue]; } -- (void)setType:(MPSiteType)aType { +- (void)setType:(MPResultType)aType { self.type_ = @(aType); } @@ -251,7 +251,7 @@ MPFixableResult result = [super findAndFixInconsistenciesInContext:context]; - if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_]) + if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_]) // Invalid self.type result = MPApplyFix( result, ^MPFixableResult { wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.", @@ -259,7 +259,7 @@ self.type = self.user.defaultType; return MPFixableResultProblemsFixed; } ); - if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_]) + if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_]) // Invalid self.user.defaultType result = MPApplyFix( result, ^MPFixableResult { wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.", @@ -270,7 +270,7 @@ if (![self isKindOfClass:[self.algorithm classOfType:self.type]]) // Mismatch between self.type and self.class result = MPApplyFix( result, ^MPFixableResult { - for (MPSiteType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);) + for (MPResultType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);) if ([self isKindOfClass:[self.algorithm classOfType:newType]]) { wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.", self.name, self.user.name, (long)self.type, self.class, (long)newType ); @@ -286,12 +286,12 @@ return result; } -- (NSUInteger)counter { +- (MPCounterValue)counter { - return [self.counter_ unsignedIntegerValue]; + return (MPCounterValue)[self.counter_ unsignedIntegerValue]; } -- (void)setCounter:(NSUInteger)aCounter { +- (void)setCounter:(MPCounterValue)aCounter { self.counter_ = @(aCounter); } @@ -354,12 +354,12 @@ self.touchID_ = @(aTouchID); } -- (MPSiteType)defaultType { +- (MPResultType)defaultType { - return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType; + return (MPResultType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType; } -- (void)setDefaultType:(MPSiteType)aDefaultType { +- (void)setDefaultType:(MPResultType)aDefaultType { self.defaultType_ = @(aDefaultType); } diff --git a/platform-darwin/Source/MPTypes.h b/platform-darwin/Source/MPTypes.h index 04b73410..c3b268ba 100644 --- a/platform-darwin/Source/MPTypes.h +++ b/platform-darwin/Source/MPTypes.h @@ -22,6 +22,7 @@ __BEGIN_DECLS extern NSString *const MPErrorDomain; extern NSInteger const MPErrorHangCode; +extern NSInteger const MPErrorMarshallCode; extern NSString *const MPSignedInNotification; extern NSString *const MPSignedOutNotification; @@ -38,13 +39,20 @@ __END_DECLS #ifdef CRASHLYTICS #define MPError(error_, message, ...) ({ \ - err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \ + NSError *error = error_; \ + err( message @"%@%@", ##__VA_ARGS__, error && [message length]? @"\n": @"", [error fullDescription]?: @"" ); \ \ if ([[MPConfig get].sendInfo boolValue]) { \ - [[Crashlytics sharedInstance] recordError:error_ withAdditionalUserInfo:@{ \ + [[Crashlytics sharedInstance] recordError:error withAdditionalUserInfo:@{ \ @"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \ }]; \ } \ + error; \ +}) +#define MPMakeError(message, ...) ({ \ + MPError( [NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ \ + NSLocalizedDescriptionKey: strf( message, ##__VA_ARGS__ ) \ + }], @"" ); \ }) #else #define MPError(error_, message, ...) ({ \ diff --git a/platform-darwin/Source/MPTypes.m b/platform-darwin/Source/MPTypes.m index 4b3829b4..1556ce9a 100644 --- a/platform-darwin/Source/MPTypes.m +++ b/platform-darwin/Source/MPTypes.m @@ -20,6 +20,7 @@ NSString *const MPErrorDomain = @"MPErrorDomain"; NSInteger const MPErrorHangCode = 1; +NSInteger const MPErrorMarshallCode = 1; NSString *const MPSignedInNotification = @"MPSignedInNotification"; NSString *const MPSignedOutNotification = @"MPSignedOutNotification"; diff --git a/platform-darwin/Source/Mac/MPSiteModel.m b/platform-darwin/Source/Mac/MPSiteModel.m index a6dfd8b7..69ac54df 100644 --- a/platform-darwin/Source/Mac/MPSiteModel.m +++ b/platform-darwin/Source/Mac/MPSiteModel.m @@ -233,14 +233,14 @@ else PearlNotMainQueue( ^{ [self updatePasswordWithResult: - [self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter - usingKey:[MPAppDelegate_Shared get].key]]; + [self.algorithm mpwTemplateForSiteNamed:self.name ofType:self.type withCounter:self.counter + usingKey:[MPAppDelegate_Shared get].key]]; [self updateLoginNameWithResult: - [self.algorithm generateLoginForSiteNamed:self.name - usingKey:[MPAppDelegate_Shared get].key]]; + [self.algorithm mpwLoginForSiteNamed:self.name + usingKey:[MPAppDelegate_Shared get].key]]; [self updateAnswerWithResult: - [self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question - usingKey:[MPAppDelegate_Shared get].key]]; + [self.algorithm mpwAnswerForSiteNamed:self.name onQuestion:self.question + usingKey:[MPAppDelegate_Shared get].key]]; } ); } @@ -252,8 +252,8 @@ [entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { [self updateLoginNameWithResult:result]; }]; - [self updateAnswerWithResult:[self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question - usingKey:[MPAppDelegate_Shared get].key]]; + [self updateAnswerWithResult:[self.algorithm mpwAnswerForSiteNamed:self.name onQuestion:self.question + usingKey:[MPAppDelegate_Shared get].key]]; } - (void)updatePasswordWithResult:(NSString *)result { diff --git a/platform-darwin/Source/Mac/MPSitesWindowController.m b/platform-darwin/Source/Mac/MPSitesWindowController.m index 9297ee1e..590446db 100644 --- a/platform-darwin/Source/Mac/MPSitesWindowController.m +++ b/platform-darwin/Source/Mac/MPSitesWindowController.m @@ -376,8 +376,8 @@ MPSiteType type = (MPSiteType)[types[t] unsignedIntegerValue]; NSString *title = [site.algorithm nameOfType:type]; if (type & MPSiteTypeClassGenerated) - title = strf( @"%@ – %@", [site.algorithm generatePasswordForSiteNamed:site.name ofType:type withCounter:site.counter - usingKey:[MPMacAppDelegate get].key], title ); + title = strf( @"%@ – %@", [site.algorithm mpwTemplateForSiteNamed:site.name ofType:type withCounter:site.counter + usingKey:[MPMacAppDelegate get].key], title ); NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0]; cell.tag = type; diff --git a/platform-darwin/Source/iOS/MPEmergencyViewController.m b/platform-darwin/Source/iOS/MPEmergencyViewController.m index 693cbff9..a425f564 100644 --- a/platform-darwin/Source/iOS/MPEmergencyViewController.m +++ b/platform-darwin/Source/iOS/MPEmergencyViewController.m @@ -137,7 +137,7 @@ - (void)updatePassword { NSString *siteName = self.siteField.text; - MPSiteType siteType = [self siteType]; + MPResultType siteType = [self siteType]; NSUInteger siteCounter = (NSUInteger)self.counterStepper.value; self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter ); @@ -147,8 +147,8 @@ [self.emergencyPasswordQueue addOperationWithBlock:^{ NSString *sitePassword = nil; if (self.key && [siteName length]) - sitePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:siteName ofType:siteType withCounter:siteCounter - usingKey:self.key]; + sitePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:siteName ofType:siteType withCounter:siteCounter + usingKey:self.key]; PearlMainQueue( ^{ [self.activity stopAnimating]; @@ -157,21 +157,21 @@ }]; } -- (enum MPSiteType)siteType { +- (MPResultType)siteType { switch (self.typeControl.selectedSegmentIndex) { case 0: - return MPSiteTypeGeneratedMaximum; + return MPResultTypeTemplateMaximum; case 1: - return MPSiteTypeGeneratedLong; + return MPResultTypeTemplateLong; case 2: - return MPSiteTypeGeneratedMedium; + return MPResultTypeTemplateMedium; case 3: - return MPSiteTypeGeneratedBasic; + return MPResultTypeTemplateBasic; case 4: - return MPSiteTypeGeneratedShort; + return MPResultTypeTemplateShort; case 5: - return MPSiteTypeGeneratedPIN; + return MPResultTypeTemplatePIN; default: Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex ); } diff --git a/platform-darwin/Source/iOS/MPPreferencesViewController.m b/platform-darwin/Source/iOS/MPPreferencesViewController.m index f679cf9f..792c310f 100644 --- a/platform-darwin/Source/iOS/MPPreferencesViewController.m +++ b/platform-darwin/Source/iOS/MPPreferencesViewController.m @@ -60,15 +60,15 @@ self.touchIDSwitch.on = activeUser.touchID; self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID]; - MPSiteType defaultType = activeUser.defaultType; + MPResultType defaultType = activeUser.defaultType; self.generated1TypeControl.selectedSegmentIndex = [self generated1SegmentIndexForType:defaultType]; self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType]; self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType]; PearlNotMainQueue( ^{ NSString *examplePassword = nil; - if (defaultType & MPSiteTypeClassGenerated) - examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType - withCounter:1 usingKey:[MPiOSAppDelegate get].key]; + if (defaultType & MPResultTypeClassTemplate) + examplePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:@"test" ofType:defaultType + withCounter:1 usingKey:[MPiOSAppDelegate get].key]; PearlMainQueue( ^{ self.typeSamplePassword.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil; } ); @@ -164,7 +164,7 @@ if (sender != self.storedTypeControl) self.storedTypeControl.selectedSegmentIndex = -1; - MPSiteType defaultType = [self typeForSelectedSegment]; + MPResultType defaultType = [self typeForSelectedSegment]; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = defaultType; [context saveToStore]; @@ -242,7 +242,7 @@ return nil; } -- (MPSiteType)typeForSelectedSegment { +- (MPResultType)typeForSelectedSegment { NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex; NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex; @@ -250,30 +250,30 @@ switch (selectedGenerated1Index) { case 0: - return MPSiteTypeGeneratedPhrase; + return MPResultTypeTemplatePhrase; case 1: - return MPSiteTypeGeneratedName; + return MPResultTypeTemplateName; default: switch (selectedGenerated2Index) { case 0: - return MPSiteTypeGeneratedMaximum; + return MPResultTypeTemplateMaximum; case 1: - return MPSiteTypeGeneratedLong; + return MPResultTypeTemplateLong; case 2: - return MPSiteTypeGeneratedMedium; + return MPResultTypeTemplateMedium; case 3: - return MPSiteTypeGeneratedBasic; + return MPResultTypeTemplateBasic; case 4: - return MPSiteTypeGeneratedShort; + return MPResultTypeTemplateShort; case 5: - return MPSiteTypeGeneratedPIN; + return MPResultTypeTemplatePIN; default: switch (selectedStoredIndex) { case 0: - return MPSiteTypeStoredPersonal; + return MPResultTypeStatefulPersonal; case 1: - return MPSiteTypeStoredDevicePrivate; + return MPResultTypeStatefulDevice; default: Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld", (long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex ); @@ -282,44 +282,44 @@ } } -- (NSInteger)generated1SegmentIndexForType:(MPSiteType)type { +- (NSInteger)generated1SegmentIndexForType:(MPResultType)type { switch (type) { - case MPSiteTypeGeneratedPhrase: + case MPResultTypeTemplatePhrase: return 0; - case MPSiteTypeGeneratedName: + case MPResultTypeTemplateName: return 1; default: return -1; } } -- (NSInteger)generated2SegmentIndexForType:(MPSiteType)type { +- (NSInteger)generated2SegmentIndexForType:(MPResultType)type { switch (type) { - case MPSiteTypeGeneratedMaximum: + case MPResultTypeTemplateMaximum: return 0; - case MPSiteTypeGeneratedLong: + case MPResultTypeTemplateLong: return 1; - case MPSiteTypeGeneratedMedium: + case MPResultTypeTemplateMedium: return 2; - case MPSiteTypeGeneratedBasic: + case MPResultTypeTemplateBasic: return 3; - case MPSiteTypeGeneratedShort: + case MPResultTypeTemplateShort: return 4; - case MPSiteTypeGeneratedPIN: + case MPResultTypeTemplatePIN: return 5; default: return -1; } } -- (NSInteger)storedSegmentIndexForType:(MPSiteType)type { +- (NSInteger)storedSegmentIndexForType:(MPResultType)type { switch (type) { - case MPSiteTypeStoredPersonal: + case MPResultTypeStatefulPersonal: return 0; - case MPSiteTypeStoredDevicePrivate: + case MPResultTypeStatefulDevice: return 1; default: return -1; diff --git a/platform-darwin/Source/iOS/MPSiteCell.m b/platform-darwin/Source/iOS/MPSiteCell.m index c41d0b6d..89dc939b 100644 --- a/platform-darwin/Source/iOS/MPSiteCell.m +++ b/platform-darwin/Source/iOS/MPSiteCell.m @@ -284,7 +284,7 @@ [PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic initSheet:^(UIActionSheet *sheet) { for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) { - MPSiteType type = (MPSiteType)[typeNumber unsignedIntegerValue]; + MPResultType type = (MPResultType)[typeNumber unsignedIntegerValue]; NSString *typeName = [mainSite.algorithm nameOfType:type]; if (type == mainSite.type) [sheet addButtonWithTitle:strf( @"● %@", typeName )]; @@ -295,7 +295,7 @@ if (buttonIndex == [sheet cancelButtonIndex]) return; - MPSiteType type = (MPSiteType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?: + MPResultType type = (MPResultType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?: mainSite.user.defaultType?: mainSite.algorithm.defaultType; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { @@ -311,7 +311,7 @@ self.loginNameField.enabled = YES; self.passwordField.enabled = YES; - if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPSiteTypeClassStored) + if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPResultTypeClassStateful) [self.passwordField becomeFirstResponder]; else [self.loginNameField becomeFirstResponder]; @@ -537,7 +537,7 @@ self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]; self.modeButton.visible = !self.transientSite; self.modeButton.alpha = settingsMode? 0.5f: 0.1f; - self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPSiteTypeClassGenerated; + self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPResultTypeClassTemplate; self.modeButton.selected = settingsMode; self.strengthLabel.gone = !settingsMode; self.modeScrollView.scrollEnabled = !self.transientSite; @@ -565,8 +565,8 @@ // Site Password self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue]; self.passwordField.attributedPlaceholder = stra( - mainSite.type & MPSiteTypeClassStored? strl( @"No password" ): - mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{ + mainSite.type & MPResultTypeClassStateful? strl( @"No password" ): + mainSite.type & MPResultTypeClassTemplate? strl( @"..." ): @"", @{ NSForegroundColorAttributeName: [UIColor whiteColor] } ); @@ -585,10 +585,10 @@ BOOL loginGenerated = site.loginGenerated; NSString *password = nil, *loginName = [site resolveLoginUsingKey:key]; - MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType; - if (self.transientSite && transientType & MPSiteTypeClassGenerated) - password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:transientType - withCounter:1 usingKey:key]; + MPResultType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType; + if (self.transientSite && transientType & MPResultTypeClassTemplate) + password = [MPAlgorithmDefault mpwTemplateForSiteNamed:self.transientSite ofType:transientType + withCounter:1 usingKey:key]; else if (site) password = [site resolvePasswordUsingKey:key]; diff --git a/platform-darwin/Source/iOS/MPTypeViewController.h b/platform-darwin/Source/iOS/MPTypeViewController.h index aceb67d0..60e43a66 100644 --- a/platform-darwin/Source/iOS/MPTypeViewController.h +++ b/platform-darwin/Source/iOS/MPTypeViewController.h @@ -23,8 +23,8 @@ @protocol MPTypeDelegate @required -- (void)didSelectType:(MPSiteType)type; -- (MPSiteType)selectedType; +- (void)didSelectType:(MPResultType)type; +- (MPResultType)selectedType; @optional - (MPSiteEntity *)selectedSite; diff --git a/platform-darwin/Source/iOS/MPTypeViewController.m b/platform-darwin/Source/iOS/MPTypeViewController.m index 148b2191..9a59b3ea 100644 --- a/platform-darwin/Source/iOS/MPTypeViewController.m +++ b/platform-darwin/Source/iOS/MPTypeViewController.m @@ -22,7 +22,7 @@ @interface MPTypeViewController() -- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath; +- (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath; @end @@ -75,11 +75,11 @@ if ([self.delegate respondsToSelector:@selector( selectedSite )]) selectedSite = [self.delegate selectedSite]; - MPSiteType cellType = [self typeAtIndexPath:indexPath]; - MPSiteType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType]; + MPResultType cellType = [self typeAtIndexPath:indexPath]; + MPResultType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType]; cell.selected = (selectedType == cellType); - if (cellType != (MPSiteType)NSNotFound && cellType & MPSiteTypeClassGenerated) { + if (cellType != (MPResultType)NSNotFound && cellType & MPResultTypeClassTemplate) { [(UITextField *)[cell viewWithTag:2] setText:@"..."]; NSString *name = selectedSite.name; @@ -88,8 +88,8 @@ counter = ((MPGeneratedSiteEntity *)selectedSite).counter; PearlNotMainQueue( ^{ - NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType - withCounter:counter usingKey:[MPiOSAppDelegate get].key]; + NSString *typeContent = [MPAlgorithmDefault mpwTemplateForSiteNamed:name ofType:cellType + withCounter:counter usingKey:[MPiOSAppDelegate get].key]; PearlMainQueue( ^{ [(UITextField *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:2] setText:typeContent]; @@ -104,8 +104,8 @@ NSAssert( self.navigationController.topViewController == self, @"Not the currently active navigation item." ); - MPSiteType type = [self typeAtIndexPath:indexPath]; - if (type == (MPSiteType)NSNotFound) + MPResultType type = [self typeAtIndexPath:indexPath]; + if (type == (MPResultType)NSNotFound) // Selected a non-type row. return; @@ -113,28 +113,28 @@ [self.navigationController popViewControllerAnimated:YES]; } -- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath { +- (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath { switch (indexPath.section) { case 0: { // Generated switch (indexPath.row) { case 0: - return (MPSiteType)NSNotFound; + return (MPResultType)NSNotFound; case 1: - return MPSiteTypeGeneratedMaximum; + return MPResultTypeTemplateMaximum; case 2: - return MPSiteTypeGeneratedLong; + return MPResultTypeTemplateLong; case 3: - return MPSiteTypeGeneratedMedium; + return MPResultTypeTemplateMedium; case 4: - return MPSiteTypeGeneratedBasic; + return MPResultTypeTemplateBasic; case 5: - return MPSiteTypeGeneratedShort; + return MPResultTypeTemplateShort; case 6: - return MPSiteTypeGeneratedPIN; + return MPResultTypeTemplatePIN; case 7: - return (MPSiteType)NSNotFound; + return (MPResultType)NSNotFound; default: { Throw( @"Unsupported row: %ld, when selecting generated site type.", (long)indexPath.row ); @@ -146,13 +146,13 @@ // Stored switch (indexPath.row) { case 0: - return (MPSiteType)NSNotFound; + return (MPResultType)NSNotFound; case 1: - return MPSiteTypeStoredPersonal; + return MPResultTypeStatefulPersonal; case 2: - return MPSiteTypeStoredDevicePrivate; + return MPResultTypeStatefulDevice; case 3: - return (MPSiteType)NSNotFound; + return (MPResultType)NSNotFound; default: { Throw( @"Unsupported row: %ld, when selecting stored site type.", (long)indexPath.row ); diff --git a/platform-darwin/Source/iOS/MPiOSAppDelegate.m b/platform-darwin/Source/iOS/MPiOSAppDelegate.m index f24b639d..8b179dff 100644 --- a/platform-darwin/Source/iOS/MPiOSAppDelegate.m +++ b/platform-darwin/Source/iOS/MPiOSAppDelegate.m @@ -20,6 +20,7 @@ #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" #import "MPStoreViewController.h" +#import "mpw-marshall.h" @interface MPiOSAppDelegate() @@ -177,62 +178,46 @@ return YES; } -- (void)importSites:(NSString *)importedSitesString { +- (void)importSites:(NSString *)importData { if ([NSThread isMainThread]) { PearlNotMainQueue( ^{ - [self importSites:importedSitesString]; + [self importSites:importData]; } ); return; } PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"]; - MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) { + [self importSites:importData askImportPassword:^NSString *(NSString *userName) { return PearlAwait( ^(void (^setResult)(id)) { - [PearlAlert showAlertWithTitle:@"Import File's Master Password" - message:strf( @"%@'s export was done using a different master password.\n" - @"Enter that master password to unlock the exported data.", userName ) + [PearlAlert showAlertWithTitle:strf( @"Importing Sites For\n%@", userName ) + message:@"Enter the master password used to create this export file." viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { if (buttonIndex_ == [alert_ cancelButtonIndex]) setResult( nil ); else setResult( [alert_ textFieldAtIndex:0].text ); - } - cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil]; + } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil]; } ); - } askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) { + } askUserPassword:^NSString *(NSString *userName) { return PearlAwait( (id)^(void (^setResult)(id)) { - [PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName ) - message:strf( @"Imports %lu sites, overwriting %lu.", - (unsigned long)importCount, (unsigned long)deleteCount ) + [PearlAlert showAlertWithTitle:strf( @"Master Password For\n%@", userName ) + message:@"Enter the current master password for this user." viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { if (buttonIndex_ == [alert_ cancelButtonIndex]) setResult( nil ); else setResult( [alert_ textFieldAtIndex:0].text ); - } - cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil]; + } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil]; } ); + } result:^(NSError *error) { + [activityOverlay cancelOverlayAnimated:YES]; + + if (error && !(error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError)) + [PearlAlert showError:error.localizedDescription]; }]; - - switch (result) { - case MPImportResultSuccess: - case MPImportResultCancelled: - break; - case MPImportResultInternalError: - [PearlAlert showError:@"Import failed because of an internal error."]; - break; - case MPImportResultMalformedInput: - [PearlAlert showError:@"The import doesn't look like a Master Password export."]; - break; - case MPImportResultInvalidPassword: - [PearlAlert showError:@"Incorrect master password for the import sites."]; - break; - } - - [activityOverlay cancelOverlayAnimated:YES]; } - (void)applicationWillEnterForeground:(UIApplication *)application { @@ -250,10 +235,9 @@ [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil]; PearlNotMainQueue( ^{ - NSString *importHeader = @"# Master Password site export"; - NSString *importedSitesString = [UIPasteboard generalPasteboard].string; - if ([importedSitesString length] > [importHeader length] && - [[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader]) + NSString *importData = [UIPasteboard generalPasteboard].string; + MPMarshallInfo *importInfo = mpw_marshall_read_info( importData.UTF8String ); + if (importInfo->format != MPMarshallFormatNone) [PearlAlert showAlertWithTitle:@"Import Sites?" message: @"We've detected Master Password import sites on your pasteboard, would you like to import them?" viewStyle:UIAlertViewStyleDefault initAlert:nil @@ -261,9 +245,10 @@ if (buttonIndex == [alert cancelButtonIndex]) return; - [self importSites:importedSitesString]; + [self importSites:importData]; [UIPasteboard generalPasteboard].string = @""; } cancelTitle:@"No" otherTitles:@"Import Sites", nil]; + mpw_marshal_info_free( importInfo ); } ); [super applicationDidBecomeActive:application]; @@ -449,62 +434,86 @@ return; } - NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords]; - NSString *message; + [self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) { + return PearlAwait( ^(void (^setResult)(id)) { + [PearlAlert showAlertWithTitle:@"Import File's Master Password" + message:strf( @"%@'s export was done using a different master password.\n" + @"Enter that master password to unlock the exported data.", userName ) + viewStyle:UIAlertViewStyleSecureTextInput + initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { + if (buttonIndex_ == [alert_ cancelButtonIndex]) + setResult( nil ); + else + setResult( [alert_ textFieldAtIndex:0].text ); + } + cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil]; + } ); + } result:^(NSString *mpsites, NSError *error) { + if (!mpsites || error) { + MPError( error, @"Failed to export mpsites." ); + [PearlAlert showAlertWithTitle:@"Export Error" + message:error.localizedDescription + viewStyle:UIAlertViewStyleDefault + initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay + otherTitles:nil]; + return; + } - if (revealPasswords) - message = strf( @"Export of Master Password sites with passwords included.\n\n" - @"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n" - @"--\n" - @"%@\n" - @"Master Password %@, build %@", - [self activeUserForMainThread].name, - [PearlInfoPlist get].CFBundleShortVersionString, - [PearlInfoPlist get].CFBundleVersion ); - else - message = strf( @"Backup of Master Password sites.\n\n\n" - @"--\n" - @"%@\n" - @"Master Password %@, build %@", - [self activeUserForMainThread].name, - [PearlInfoPlist get].CFBundleShortVersionString, - [PearlInfoPlist get].CFBundleVersion ); + [PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil + tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { + if (buttonIndex == [sheet cancelButtonIndex]) + return; - NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; - [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"]; + NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; + [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"]; + NSString *exportFileName = strf( @"%@ (%@).mpsites", + [self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] ); - NSString *exportFileName = strf( @"%@ (%@).mpsites", - [self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] ); - [PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil - tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { - if (buttonIndex == [sheet cancelButtonIndex]) - return; + if (buttonIndex == [sheet firstOtherButtonIndex]) { + NSString *message; + if (revealPasswords) + message = strf( @"Export of Master Password sites with passwords included.\n\n" + @"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n" + @"--\n" + @"%@\n" + @"Master Password %@, build %@", + [self activeUserForMainThread].name, + [PearlInfoPlist get].CFBundleShortVersionString, + [PearlInfoPlist get].CFBundleVersion ); + else + message = strf( @"Backup of Master Password sites.\n\n\n" + @"--\n" + @"%@\n" + @"Master Password %@, build %@", + [self activeUserForMainThread].name, + [PearlInfoPlist get].CFBundleShortVersionString, + [PearlInfoPlist get].CFBundleVersion ); - if (buttonIndex == [sheet firstOtherButtonIndex]) { - [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message - attachments:[[PearlEMailAttachment alloc] - initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] - mimeType:@"text/plain" fileName:exportFileName], - nil]; - return; - } + [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message + attachments:[[PearlEMailAttachment alloc] + initWithContent:[mpsites dataUsingEncoding:NSUTF8StringEncoding] + mimeType:@"text/plain" fileName:exportFileName], + nil]; + return; + } - NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory - inDomains:NSUserDomainMask] lastObject]; - NSURL *exportURL = [[applicationSupportURL - URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES] - URLByAppendingPathComponent:exportFileName isDirectory:NO]; - NSError *error = nil; - if (![[exportedSites dataUsingEncoding:NSUTF8StringEncoding] - writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&error]) - MPError( error, @"Failed to write export data to URL %@.", exportURL ); - else { - self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL]; - self.interactionController.UTI = @"com.lyndir.masterpassword.sites"; - self.interactionController.delegate = self; - [self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES]; - } - } cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil]; + NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory + inDomains:NSUserDomainMask] lastObject]; + NSURL *exportURL = [[applicationSupportURL + URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES] + URLByAppendingPathComponent:exportFileName isDirectory:NO]; + NSError *writeError = nil; + if (![[mpsites dataUsingEncoding:NSUTF8StringEncoding] + writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError]) + MPError( writeError, @"Failed to write export data to URL %@.", exportURL ); + else { + self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL]; + self.interactionController.UTI = @"com.lyndir.masterpassword.sites"; + self.interactionController.delegate = self; + [self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES]; + } + } cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil]; + }]; } - (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset { diff --git a/platform-independent/cli-c/cli/mpw-cli.c b/platform-independent/cli-c/cli/mpw-cli.c index ce1f5a4e..bb3f7a8a 100644 --- a/platform-independent/cli-c/cli/mpw-cli.c +++ b/platform-independent/cli-c/cli/mpw-cli.c @@ -21,6 +21,7 @@ #endif #define MP_ENV_fullName "MP_FULLNAME" #define MP_ENV_algorithm "MP_ALGORITHM" +#define MP_ENV_format "MP_FORMAT" static void usage() { @@ -75,10 +76,11 @@ static void usage() { " -f|F format The mpsites format to use for reading/writing site parameters.\n" " -F forces the use of the given format,\n" " -f allows fallback/migration.\n" - " Defaults to json, falls back to plain.\n" + " Defaults to %s in env or json, falls back to plain.\n" + " n, none | No file\n" " f, flat | ~/.mpw.d/Full Name.%s\n" " j, json | ~/.mpw.d/Full Name.%s\n\n", - mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) ); + MP_ENV_format, mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) ); inf( "" " -R redacted Whether to save the mpsites in redacted format or not.\n" " Defaults to 1, redacted.\n\n" ); @@ -166,6 +168,7 @@ int main(int argc, char *const argv[]) { const char *keyPurposeArg = NULL, *keyContextArg = NULL, *sitesFormatArg = NULL, *sitesRedactedArg = NULL; fullNameArg = mpw_getenv( MP_ENV_fullName ); algorithmVersionArg = mpw_getenv( MP_ENV_algorithm ); + sitesFormatArg = mpw_getenv( MP_ENV_format ); // Read the command-line options. for (int opt; (opt = getopt( argc, argv, "u:U:M:t:P:c:a:s:p:C:f:F:R:vqh" )) != EOF;) @@ -269,13 +272,14 @@ int main(int argc, char *const argv[]) { char *sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) ); if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) { dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); - free( sitesPath ); // Try to fall back to the flat format. if (!sitesFormatFixed) { - sitesFormat = MPMarshallFormatFlat; - sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) ); - if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) + free( sitesPath ); + sitesPath = mpw_path( fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) ); + if (sitesPath && (sitesFile = fopen( sitesPath, "r" ))) + sitesFormat = MPMarshallFormatFlat; + else dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); } } @@ -290,9 +294,9 @@ int main(int argc, char *const argv[]) { else { // Read file. size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0; - char *buf = NULL; - while ((mpw_realloc( &buf, &bufSize, readAmount )) && - (bufOffset += (readSize = fread( buf + bufOffset, 1, readAmount, sitesFile ))) && + char *sitesInputData = NULL; + while ((mpw_realloc( &sitesInputData, &bufSize, readAmount )) && + (bufOffset += (readSize = fread( sitesInputData + bufOffset, 1, readAmount, sitesFile ))) && (readSize == readAmount)); if (ferror( sitesFile )) wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) ); @@ -300,13 +304,14 @@ int main(int argc, char *const argv[]) { // Parse file. MPMarshallError marshallError = { .type = MPMarshallSuccess }; - user = mpw_marshall_read( buf, sitesFormat, masterPassword, &marshallError ); + MPMarshallFormat sitesInputFormat = sitesFormatArg? sitesFormat: mpw_marshall_format_guess( sitesInputData ); + user = mpw_marshall_read( sitesInputData, sitesInputFormat, masterPassword, &marshallError ); if (marshallError.type == MPMarshallErrorMasterPassword) { // Incorrect master password. if (!allowPasswordUpdate) { ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description ); mpw_marshal_free( user ); - mpw_free( buf, bufSize ); + mpw_free( sitesInputData, bufSize ); free( sitesPath ); return EX_DATAERR; } @@ -321,14 +326,14 @@ int main(int argc, char *const argv[]) { importMasterPassword = mpw_getpass( "Old master password: " ); mpw_marshal_free( user ); - user = mpw_marshall_read( buf, sitesFormat, importMasterPassword, &marshallError ); + user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError ); } if (user) { mpw_free_string( user->masterPassword ); user->masterPassword = strdup( masterPassword ); } } - mpw_free( buf, bufSize ); + mpw_free( sitesInputData, bufSize ); if (!user || marshallError.type != MPMarshallSuccess) { err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description ); mpw_marshal_free( user ); @@ -471,7 +476,7 @@ int main(int argc, char *const argv[]) { if (keyPurpose == MPKeyPurposeIdentification && site && !site->loginGenerated && site->loginName) fprintf( stdout, "%s\n", site->loginName ); - else if (resultParam && site && resultType & MPResultTypeClassState) { + else if (resultParam && site && resultType & MPResultTypeClassStateful) { mpw_free_string( site->content ); if (!(site->content = mpw_siteState( masterKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam, algorithmVersion ))) { @@ -483,7 +488,7 @@ int main(int argc, char *const argv[]) { inf( "saved.\n" ); } else { - if (!resultParam && site && site->content && resultType & MPResultTypeClassState) + if (!resultParam && site && site->content && resultType & MPResultTypeClassStateful) resultParam = strdup( site->content ); const char *siteResult = mpw_siteResult( masterKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam, algorithmVersion );