Add marshalling metadata lookup & adapt iOS for new APIs.
This commit is contained in:
parent
c0ba96daa2
commit
f5c7d11f0e
@ -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 );
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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 = "<group>"; };
|
||||
DAB7AE741F3D755B00C856B1 /* vasprintf_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vasprintf_compat.h; sourceTree = "<group>"; };
|
||||
DAB7AE761F3D755B00C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libjson-c.a"; sourceTree = "<group>"; };
|
||||
DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall-util.h"; sourceTree = "<group>"; };
|
||||
DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall-util.c"; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
DABD36101711E29400CF925C /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
|
||||
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "ARGS: $*"
|
||||
|
||||
cd "${BASH_SOURCE%/*}/../External/libjson-c"
|
||||
[[ -d libjson-c-ios ]] && exit
|
||||
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "ARGS: $*"
|
||||
|
||||
cd "${BASH_SOURCE%/*}/../External/libjson-c"
|
||||
[[ -d libjson-c-osx ]] && exit
|
||||
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "ARGS: $*"
|
||||
|
||||
cd "${BASH_SOURCE%/*}/../External/libsodium"
|
||||
[[ -d libsodium-ios ]] && exit
|
||||
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "ARGS: $*"
|
||||
|
||||
cd "${BASH_SOURCE%/*}/../External/libsodium"
|
||||
[[ -d libsodium-osx ]] && exit
|
||||
|
@ -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
|
||||
|
@ -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<MPAlgorithm> 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<MPAlgorithm> 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<MPAlgorithm> 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<MPAlgorithm> 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<MPAlgorithm> 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 );
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<MPAlgorithm> 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<MPAlgorithm> 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<MPAlgorithm> 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<MPAlgorithm> 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
|
||||
|
@ -48,7 +48,7 @@
|
||||
@interface MPSiteEntity(MP)<MPFixable>
|
||||
|
||||
@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<MPAlgorithm> algorithm;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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, ...) ({ \
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
NSString *const MPErrorDomain = @"MPErrorDomain";
|
||||
NSInteger const MPErrorHangCode = 1;
|
||||
NSInteger const MPErrorMarshallCode = 1;
|
||||
|
||||
NSString *const MPSignedInNotification = @"MPSignedInNotification";
|
||||
NSString *const MPSignedOutNotification = @"MPSignedOutNotification";
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
||||
|
@ -23,8 +23,8 @@
|
||||
@protocol MPTypeDelegate<NSObject>
|
||||
|
||||
@required
|
||||
- (void)didSelectType:(MPSiteType)type;
|
||||
- (MPSiteType)selectedType;
|
||||
- (void)didSelectType:(MPResultType)type;
|
||||
- (MPResultType)selectedType;
|
||||
|
||||
@optional
|
||||
- (MPSiteEntity *)selectedSite;
|
||||
|
@ -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 );
|
||||
|
@ -20,6 +20,7 @@
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPStoreViewController.h"
|
||||
#import "mpw-marshall.h"
|
||||
|
||||
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
|
||||
|
||||
@ -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 {
|
||||
|
@ -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 );
|
||||
|
Loading…
Reference in New Issue
Block a user