2
0

Add marshalling metadata lookup & adapt iOS for new APIs.

This commit is contained in:
Maarten Billemont 2017-08-12 21:57:47 -04:00
parent c0ba96daa2
commit f5c7d11f0e
35 changed files with 821 additions and 886 deletions

View File

@ -102,7 +102,7 @@ const char *mpw_siteResult(
return NULL; return NULL;
} }
} }
else if (resultType & MPResultTypeClassState) { else if (resultType & MPResultTypeClassStateful) {
switch (algorithmVersion) { switch (algorithmVersion) {
case MPAlgorithmVersion0: case MPAlgorithmVersion0:
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam ); return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam );

View File

@ -21,7 +21,7 @@
#include "mpw-marshall-util.h" #include "mpw-marshall-util.h"
#include "mpw-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. // Skip leading spaces.
for (; **in == ' '; ++*in); for (; **in == ' '; ++*in);

View File

@ -30,7 +30,7 @@
* The input string reference is advanced beyond the token delimitor if one is found. * 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. */ * @return A new string containing the token or NULL if the delim wasn't found before eol. */
char *mpw_get_token( 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. */ /** Convert an RFC 3339 time string into epoch time. */
time_t mpw_mktime( time_t mpw_mktime(
const char *time); const char *time);

View File

@ -89,6 +89,20 @@ MPMarshalledQuestion *mpw_marshal_question(
return 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( bool mpw_marshal_free(
MPMarshalledUser *user) { MPMarshalledUser *user) {
@ -313,6 +327,9 @@ bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) { char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) {
switch (outFormat) { switch (outFormat) {
case MPMarshallFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return false;
case MPMarshallFormatFlat: case MPMarshallFormatFlat:
return mpw_marshall_write_flat( out, user, error ); return mpw_marshall_write_flat( out, user, error );
case MPMarshallFormatJSON: 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( 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." }; *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. // Parse import data.
MPMasterKey masterKey = NULL; MPMasterKey masterKey = NULL;
@ -336,7 +406,7 @@ static MPMarshalledUser *mpw_marshall_read_flat(
MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1; MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1;
MPResultType defaultType = MPResultTypeDefault; MPResultType defaultType = MPResultTypeDefault;
bool headerStarted = false, headerEnded = false, importRedacted = false; 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 // Comment or header
if (*positionInLine == '#') { if (*positionInLine == '#') {
@ -541,10 +611,39 @@ static MPMarshalledUser *mpw_marshall_read_flat(
return user; 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( 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." }; *error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) {
error->type = MPMarshallErrorStructure;
error->description = mpw_str( "No input data." );
return NULL;
}
// Parse JSON. // Parse JSON.
enum json_tokener_error json_error = json_tokener_success; enum json_tokener_error json_error = json_tokener_success;
@ -683,10 +782,33 @@ static MPMarshalledUser *mpw_marshall_read_json(
return user; 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( 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) { switch (inFormat) {
case MPMarshallFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return false;
case MPMarshallFormatFlat: case MPMarshallFormatFlat:
return mpw_marshall_read_flat( in, masterPassword, error ); return mpw_marshall_read_flat( in, masterPassword, error );
case MPMarshallFormatJSON: case MPMarshallFormatJSON:
@ -700,6 +822,9 @@ MPMarshalledUser *mpw_marshall_read(
const MPMarshallFormat mpw_formatWithName( const MPMarshallFormat mpw_formatWithName(
const char *formatName) { const char *formatName) {
if (!formatName || !strlen( formatName ))
return MPMarshallFormatNone;
// Lower-case to standardize it. // Lower-case to standardize it.
size_t stdFormatNameSize = strlen( formatName ); size_t stdFormatNameSize = strlen( formatName );
char stdFormatName[stdFormatNameSize + 1]; char stdFormatName[stdFormatNameSize + 1];
@ -707,6 +832,8 @@ const MPMarshallFormat mpw_formatWithName(
stdFormatName[c] = (char)tolower( formatName[c] ); stdFormatName[c] = (char)tolower( formatName[c] );
stdFormatName[stdFormatNameSize] = '\0'; stdFormatName[stdFormatNameSize] = '\0';
if (strncmp( mpw_nameForFormat( MPMarshallFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatNone;
if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0) if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatFlat; return MPMarshallFormatFlat;
if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0) if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
@ -720,6 +847,8 @@ const char *mpw_nameForFormat(
const MPMarshallFormat format) { const MPMarshallFormat format) {
switch (format) { switch (format) {
case MPMarshallFormatNone:
return "none";
case MPMarshallFormatFlat: case MPMarshallFormatFlat:
return "flat"; return "flat";
case MPMarshallFormatJSON: case MPMarshallFormatJSON:
@ -735,6 +864,8 @@ const char *mpw_marshall_format_extension(
const MPMarshallFormat format) { const MPMarshallFormat format) {
switch (format) { switch (format) {
case MPMarshallFormatNone:
return NULL;
case MPMarshallFormatFlat: case MPMarshallFormatFlat:
return "mpsites"; return "mpsites";
case MPMarshallFormatJSON: case MPMarshallFormatJSON:

View File

@ -26,6 +26,8 @@
//// Types. //// Types.
typedef enum( unsigned int, MPMarshallFormat ) { typedef enum( unsigned int, MPMarshallFormat ) {
/** Generate a key for authentication. */
MPMarshallFormatNone,
/** Generate a key for authentication. */ /** Generate a key for authentication. */
MPMarshallFormatFlat, MPMarshallFormatFlat,
/** Generate a name for identification. */ /** Generate a name for identification. */
@ -91,22 +93,42 @@ typedef struct MPMarshalledUser {
MPMarshalledSite *sites; MPMarshalledSite *sites;
} MPMarshalledUser; } MPMarshalledUser;
typedef struct MPMarshallInfo {
MPMarshallFormat format;
MPAlgorithmVersion algorithm;
const char *fullName;
const char *keyID;
bool redacted;
time_t date;
} MPMarshallInfo;
//// Marshalling. //// Marshalling.
/** Write the user and all associated data out to the given output buffer using the given marshalling format. */
bool mpw_marshall_write( bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error); 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( 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. //// Utilities.
/** Create a new user object ready for marshalling. */
MPMarshalledUser *mpw_marshall_user( MPMarshalledUser *mpw_marshall_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion); 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( MPMarshalledSite *mpw_marshall_site(
MPMarshalledUser *user, MPMarshalledUser *user,
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion); 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( MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword); 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( bool mpw_marshal_free(
MPMarshalledUser *user); MPMarshalledUser *user);
@ -122,6 +144,9 @@ const MPMarshallFormat mpw_formatWithName(
*/ */
const char *mpw_nameForFormat( const char *mpw_nameForFormat(
const MPMarshallFormat format); 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 char *mpw_marshall_format_extension(
const MPMarshallFormat format); const MPMarshallFormat format);

View File

@ -48,9 +48,9 @@ const MPResultType mpw_typeWithName(const char *typeName) {
if ('n' == typeName[0]) if ('n' == typeName[0])
return MPResultTypeTemplateName; return MPResultTypeTemplateName;
if ('P' == typeName[0]) if ('P' == typeName[0])
return MPResultTypeStatePersonal; return MPResultTypeStatefulPersonal;
if ('D' == typeName[0]) if ('D' == typeName[0])
return MPResultTypeStateDevice; return MPResultTypeStatefulDevice;
if ('k' == typeName[0]) if ('k' == typeName[0])
return MPResultTypeDeriveKey; return MPResultTypeDeriveKey;
} }
@ -82,10 +82,10 @@ const MPResultType mpw_typeWithName(const char *typeName) {
return MPResultTypeTemplateName; return MPResultTypeTemplateName;
if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0) if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeTemplatePhrase; return MPResultTypeTemplatePhrase;
if (strncmp( mpw_nameForType( MPResultTypeStatePersonal ), stdTypeName, strlen( stdTypeName ) ) == 0) if (strncmp( mpw_nameForType( MPResultTypeStatefulPersonal ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeStatePersonal; return MPResultTypeStatefulPersonal;
if (strncmp( mpw_nameForType( MPResultTypeStateDevice ), stdTypeName, strlen( stdTypeName ) ) == 0) if (strncmp( mpw_nameForType( MPResultTypeStatefulDevice ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeStateDevice; return MPResultTypeStatefulDevice;
if (strncmp( mpw_nameForType( MPResultTypeDeriveKey ), stdTypeName, strlen( stdTypeName ) ) == 0) if (strncmp( mpw_nameForType( MPResultTypeDeriveKey ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeDeriveKey; return MPResultTypeDeriveKey;
@ -112,9 +112,9 @@ const char *mpw_nameForType(MPResultType resultType) {
return "name"; return "name";
case MPResultTypeTemplatePhrase: case MPResultTypeTemplatePhrase:
return "phrase"; return "phrase";
case MPResultTypeStatePersonal: case MPResultTypeStatefulPersonal:
return "personal"; return "personal";
case MPResultTypeStateDevice: case MPResultTypeStatefulDevice:
return "device"; return "device";
case MPResultTypeDeriveKey: case MPResultTypeDeriveKey:
return "key"; return "key";

View File

@ -51,7 +51,7 @@ typedef enum( uint16_t, MPResultTypeClass ) {
/** Use the site key to generate a password from a template. */ /** Use the site key to generate a password from a template. */
MPResultTypeClassTemplate = 1 << 4, MPResultTypeClassTemplate = 1 << 4,
/** Use the site key to encrypt and decrypt a stateful entity. */ /** 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. */ /** Use the site key to derive a site-specific object. */
MPResultTypeClassDerive = 1 << 6, MPResultTypeClassDerive = 1 << 6,
}; };
@ -86,9 +86,9 @@ typedef enum( uint32_t, MPResultType ) {
MPResultTypeTemplatePhrase = 0xF | MPResultTypeClassTemplate | 0x0, MPResultTypeTemplatePhrase = 0xF | MPResultTypeClassTemplate | 0x0,
/** Custom saved password. */ /** Custom saved password. */
MPResultTypeStatePersonal = 0x0 | MPResultTypeClassState | MPSiteFeatureExportContent, MPResultTypeStatefulPersonal = 0x0 | MPResultTypeClassStateful | MPSiteFeatureExportContent,
/** Custom saved password that should not be exported from the device. */ /** Custom saved password that should not be exported from the device. */
MPResultTypeStateDevice = 0x1 | MPResultTypeClassState | MPSiteFeatureDevicePrivate, MPResultTypeStatefulDevice = 0x1 | MPResultTypeClassStateful | MPSiteFeatureDevicePrivate,
/** Derive a unique binary key. */ /** Derive a unique binary key. */
MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative, MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative,

View File

@ -35,7 +35,9 @@
#include "mpw-util.h" #include "mpw-util.h"
#ifdef inf_level
int mpw_verbosity = 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) { bool mpw_push_buf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) {

View File

@ -253,6 +253,7 @@
DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */; }; 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 */; }; 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 */; }; 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 */; }; DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; }; 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 */; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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 */, 93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */,
93D3969393A3A46BD27D7078 /* mpw-algorithm.c */, 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */,
93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */, 93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */,
DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */,
DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */,
DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */, DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */,
DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */, DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */,
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */, 93D392C5A6572DB0EB5B82C8 /* mpw-types.c */,
@ -4030,6 +4035,7 @@
DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */, DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */,
93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */, 93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */,
93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */, 93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */,
DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */,
DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */, DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */,
93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */, 93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */,
DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */, DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */,
@ -4327,6 +4333,8 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)", "$(inherited)",
); );
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@ -4510,6 +4518,8 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)", "$(inherited)",
); );
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@ -4598,6 +4608,8 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)", "$(inherited)",
); );
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;

View File

@ -2979,8 +2979,9 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include",
); );
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
@ -3315,8 +3316,9 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include",
); );
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
@ -3407,8 +3409,9 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include",
); );
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";

View File

@ -1,6 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
echo "ARGS: $*"
cd "${BASH_SOURCE%/*}/../External/libjson-c" cd "${BASH_SOURCE%/*}/../External/libjson-c"
[[ -d libjson-c-ios ]] && exit [[ -d libjson-c-ios ]] && exit

View File

@ -1,6 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
echo "ARGS: $*"
cd "${BASH_SOURCE%/*}/../External/libjson-c" cd "${BASH_SOURCE%/*}/../External/libjson-c"
[[ -d libjson-c-osx ]] && exit [[ -d libjson-c-osx ]] && exit

View File

@ -1,6 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
echo "ARGS: $*"
cd "${BASH_SOURCE%/*}/../External/libsodium" cd "${BASH_SOURCE%/*}/../External/libsodium"
[[ -d libsodium-ios ]] && exit [[ -d libsodium-ios ]] && exit

View File

@ -1,6 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
echo "ARGS: $*"
cd "${BASH_SOURCE%/*}/../External/libsodium" cd "${BASH_SOURCE%/*}/../External/libsodium"
[[ -d libsodium-osx ]] && exit [[ -d libsodium-osx ]] && exit

View File

@ -52,49 +52,44 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
- (NSData *)keyIDForKey:(MPMasterKey)masterKey; - (NSData *)keyIDForKey:(MPMasterKey)masterKey;
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword; - (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (NSString *)nameOfType:(MPSiteType)type; - (NSString *)nameOfType:(MPResultType)type;
- (NSString *)shortNameOfType:(MPSiteType)type; - (NSString *)shortNameOfType:(MPResultType)type;
- (NSString *)classNameOfType:(MPSiteType)type; - (NSString *)classNameOfType:(MPResultType)type;
- (Class)classOfType:(MPSiteType)type; - (Class)classOfType:(MPResultType)type;
- (NSArray *)allTypes; - (NSArray *)allTypes;
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType; - (NSArray *)allTypesStartingWith:(MPResultType)startingType;
- (MPSiteType)defaultType; - (MPResultType)defaultType;
- (MPSiteType)nextType:(MPSiteType)type; - (MPResultType)nextType:(MPResultType)type;
- (MPSiteType)previousType:(MPSiteType)type; - (MPResultType)previousType:(MPResultType)type;
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key; - (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
usingKey:(MPKey *)key; withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key; - (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key; withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key;
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key; - (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)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; - (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
- (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
result:(void ( ^ )(NSString *result))resultBlock; result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey - (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock; result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey - (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock; result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey - (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock; result:(void ( ^ )(NSString *result))resultBlock;
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey - (void)importPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site - (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
usingKey:(MPKey *)siteKey;
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (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; - (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
@end @end

View File

@ -145,125 +145,125 @@ static NSOperationQueue *_mpwQueue = nil;
return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256]; return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256];
} }
- (NSString *)nameOfType:(MPSiteType)type { - (NSString *)nameOfType:(MPResultType)type {
if (!type) if (!type)
return nil; return nil;
switch (type) { switch (type) {
case MPSiteTypeGeneratedMaximum: case MPResultTypeTemplateMaximum:
return @"Maximum Security Password"; return @"Maximum Security Password";
case MPSiteTypeGeneratedLong: case MPResultTypeTemplateLong:
return @"Long Password"; return @"Long Password";
case MPSiteTypeGeneratedMedium: case MPResultTypeTemplateMedium:
return @"Medium Password"; return @"Medium Password";
case MPSiteTypeGeneratedBasic: case MPResultTypeTemplateBasic:
return @"Basic Password"; return @"Basic Password";
case MPSiteTypeGeneratedShort: case MPResultTypeTemplateShort:
return @"Short Password"; return @"Short Password";
case MPSiteTypeGeneratedPIN: case MPResultTypeTemplatePIN:
return @"PIN"; return @"PIN";
case MPSiteTypeGeneratedName: case MPResultTypeTemplateName:
return @"Name"; return @"Name";
case MPSiteTypeGeneratedPhrase: case MPResultTypeTemplatePhrase:
return @"Phrase"; return @"Phrase";
case MPSiteTypeStoredPersonal: case MPResultTypeStatefulPersonal:
return @"Personal Password"; return @"Personal Password";
case MPSiteTypeStoredDevicePrivate: case MPResultTypeStatefulDevice:
return @"Device Private Password"; return @"Device Private Password";
} }
Throw( @"Type not supported: %lu", (long)type ); Throw( @"Type not supported: %lu", (long)type );
} }
- (NSString *)shortNameOfType:(MPSiteType)type { - (NSString *)shortNameOfType:(MPResultType)type {
if (!type) if (!type)
return nil; return nil;
switch (type) { switch (type) {
case MPSiteTypeGeneratedMaximum: case MPResultTypeTemplateMaximum:
return @"Maximum"; return @"Maximum";
case MPSiteTypeGeneratedLong: case MPResultTypeTemplateLong:
return @"Long"; return @"Long";
case MPSiteTypeGeneratedMedium: case MPResultTypeTemplateMedium:
return @"Medium"; return @"Medium";
case MPSiteTypeGeneratedBasic: case MPResultTypeTemplateBasic:
return @"Basic"; return @"Basic";
case MPSiteTypeGeneratedShort: case MPResultTypeTemplateShort:
return @"Short"; return @"Short";
case MPSiteTypeGeneratedPIN: case MPResultTypeTemplatePIN:
return @"PIN"; return @"PIN";
case MPSiteTypeGeneratedName: case MPResultTypeTemplateName:
return @"Name"; return @"Name";
case MPSiteTypeGeneratedPhrase: case MPResultTypeTemplatePhrase:
return @"Phrase"; return @"Phrase";
case MPSiteTypeStoredPersonal: case MPResultTypeStatefulPersonal:
return @"Personal"; return @"Personal";
case MPSiteTypeStoredDevicePrivate: case MPResultTypeStatefulDevice:
return @"Device"; return @"Device";
} }
Throw( @"Type not supported: %lu", (long)type ); Throw( @"Type not supported: %lu", (long)type );
} }
- (NSString *)classNameOfType:(MPSiteType)type { - (NSString *)classNameOfType:(MPResultType)type {
return NSStringFromClass( [self classOfType:type] ); return NSStringFromClass( [self classOfType:type] );
} }
- (Class)classOfType:(MPSiteType)type { - (Class)classOfType:(MPResultType)type {
if (!type) if (!type)
Throw( @"No type given." ); Throw( @"No type given." );
switch (type) { switch (type) {
case MPSiteTypeGeneratedMaximum: case MPResultTypeTemplateMaximum:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedLong: case MPResultTypeTemplateLong:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedMedium: case MPResultTypeTemplateMedium:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedBasic: case MPResultTypeTemplateBasic:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedShort: case MPResultTypeTemplateShort:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedPIN: case MPResultTypeTemplatePIN:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedName: case MPResultTypeTemplateName:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedPhrase: case MPResultTypeTemplatePhrase:
return [MPGeneratedSiteEntity class]; return [MPGeneratedSiteEntity class];
case MPSiteTypeStoredPersonal: case MPResultTypeStatefulPersonal:
return [MPStoredSiteEntity class]; return [MPStoredSiteEntity class];
case MPSiteTypeStoredDevicePrivate: case MPResultTypeStatefulDevice:
return [MPStoredSiteEntity class]; return [MPStoredSiteEntity class];
} }
@ -272,13 +272,13 @@ static NSOperationQueue *_mpwQueue = nil;
- (NSArray *)allTypes { - (NSArray *)allTypes {
return [self allTypesStartingWith:MPSiteTypeGeneratedPhrase]; return [self allTypesStartingWith:MPResultTypeTemplatePhrase];
} }
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType { - (NSArray *)allTypesStartingWith:(MPResultType)startingType {
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8]; NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
MPSiteType currentType = startingType; MPResultType currentType = startingType;
do { do {
[allTypes addObject:@(currentType)]; [allTypes addObject:@(currentType)];
} while ((currentType = [self nextType:currentType]) != startingType); } while ((currentType = [self nextType:currentType]) != startingType);
@ -286,199 +286,170 @@ static NSOperationQueue *_mpwQueue = nil;
return allTypes; return allTypes;
} }
- (MPSiteType)defaultType { - (MPResultType)defaultType {
return MPSiteTypeGeneratedLong; return MPResultTypeTemplateLong;
} }
- (MPSiteType)nextType:(MPSiteType)type { - (MPResultType)nextType:(MPResultType)type {
switch (type) { switch (type) {
case MPSiteTypeGeneratedPhrase: case MPResultTypeTemplatePhrase:
return MPSiteTypeGeneratedName; return MPResultTypeTemplateName;
case MPSiteTypeGeneratedName: case MPResultTypeTemplateName:
return MPSiteTypeGeneratedMaximum; return MPResultTypeTemplateMaximum;
case MPSiteTypeGeneratedMaximum: case MPResultTypeTemplateMaximum:
return MPSiteTypeGeneratedLong; return MPResultTypeTemplateLong;
case MPSiteTypeGeneratedLong: case MPResultTypeTemplateLong:
return MPSiteTypeGeneratedMedium; return MPResultTypeTemplateMedium;
case MPSiteTypeGeneratedMedium: case MPResultTypeTemplateMedium:
return MPSiteTypeGeneratedBasic; return MPResultTypeTemplateBasic;
case MPSiteTypeGeneratedBasic: case MPResultTypeTemplateBasic:
return MPSiteTypeGeneratedShort; return MPResultTypeTemplateShort;
case MPSiteTypeGeneratedShort: case MPResultTypeTemplateShort:
return MPSiteTypeGeneratedPIN; return MPResultTypeTemplatePIN;
case MPSiteTypeGeneratedPIN: case MPResultTypeTemplatePIN:
return MPSiteTypeStoredPersonal; return MPResultTypeStatefulPersonal;
case MPSiteTypeStoredPersonal: case MPResultTypeStatefulPersonal:
return MPSiteTypeStoredDevicePrivate; return MPResultTypeStatefulDevice;
case MPSiteTypeStoredDevicePrivate: case MPResultTypeStatefulDevice:
return MPSiteTypeGeneratedPhrase; return MPResultTypeTemplatePhrase;
} }
return [self defaultType]; 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) while ((nextType = [self nextType:nextType]) != type)
previousType = nextType; previousType = nextType;
return previousType; return previousType;
} }
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key { - (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1 return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplateName parameter:nil withCounter:1
variant:MPKeyPurposeIdentification context:nil usingKey:key]; variant:MPKeyPurposeIdentification context:nil usingKey:key];
} }
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
usingKey:(MPKey *)key { withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:type withCounter:counter return [self mpwResultForSiteNamed:name ofType:type parameter:nil withCounter:counter
variant:MPKeyPurposeAuthentication context:nil usingKey:key]; 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 return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplatePhrase parameter:nil withCounter:1
variant:MPKeyPurposeRecovery context:question usingKey:key]; variant:MPKeyPurposeRecovery context:question usingKey:key];
} }
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key {
__block NSString *content = nil; __block NSString *result = nil;
[self mpw_perform:^{ [self mpw_perform:^{
char const *contentBytes = mpw_passwordForSite( [key keyForAlgorithm:self], char const *resultBytes = mpw_siteResult( [key keyForAlgorithm:self],
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] ); name.UTF8String, (uint32_t)counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] );
if (contentBytes) { if (resultBytes) {
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding]; result = [NSString stringWithCString:resultBytes encoding:NSUTF8StringEncoding];
mpw_free_string( contentBytes ); 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; if (!(site.type & MPResultTypeClassStateful)) {
} wrn( @"Can only save content to site with a stateful type: %lu.", (long)site.type );
- (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; return NO;
} }
case MPSiteTypeStoredPersonal: { NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (![site isKindOfClass:[MPStoredSiteEntity class]]) { if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] ); (long)site.type, [site class] );
return NO; return NO;
} }
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize]; __block NSData *state = nil;
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] if (plainText)
encryptWithSymmetricKey:encryptionKey padding:YES]; [self mpw_perform:^{
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent]) char const *stateBytes = mpw_siteState( [key keyForAlgorithm:self], site.name.UTF8String,
return NO; MPCounterValueInitial, MPKeyPurposeAuthentication, NULL, site.type, plainText.UTF8String, [self version] );
if (stateBytes) {
((MPStoredSiteEntity *)site).contentObject = encryptedContent; state = [[NSString stringWithCString:stateBytes encoding:NSUTF8StringEncoding] decodeBase64];
return YES; mpw_free_string( stateBytes );
}
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]; NSDictionary *siteQuery = [self queryForSite:site];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] if (!state)
encryptWithSymmetricKey:encryptionKey padding:YES];
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
if (!encryptedContent)
[PearlKeyChain deleteItemForQuery:siteQuery]; [PearlKeyChain deleteItemForQuery:siteQuery];
else else
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{ [PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
(__bridge id)kSecValueData : encryptedContent, (__bridge id)kSecValueData : state,
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible:
site.type & MPSiteFeatureDevicePrivate? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
: (__bridge id)kSecAttrAccessibleWhenUnlocked,
#endif #endif
}]; }];
((MPStoredSiteEntity *)site).contentObject = nil; ((MPStoredSiteEntity *)site).contentObject = nil;
return YES; return YES;
} }
}
Throw( @"Unsupported type: %ld", (long)site.type ); - (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
}
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
return PearlAwait( ^(void (^setResult)(id)) { return PearlAwait( ^(void (^setResult)(id)) {
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) { [self resolveLoginForSite:site usingKey:key result:^(NSString *result_) {
setResult( result_ ); setResult( result_ );
}]; }];
} ); } );
} }
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
return PearlAwait( ^(void (^setResult)(id)) { return PearlAwait( ^(void (^setResult)(id)) {
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) { [self resolvePasswordForSite:site usingKey:key result:^(NSString *result_) {
setResult( result_ ); setResult( result_ );
}]; }];
} ); } );
} }
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
return PearlAwait( ^(void (^setResult)(id)) { return PearlAwait( ^(void (^setResult)(id)) {
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) { [self resolveAnswerForSite:site usingKey:key result:^(NSString *result_) {
setResult( result_ ); setResult( result_ );
}]; }];
} ); } );
} }
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey { - (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key {
return PearlAwait( ^(void (^setResult)(id)) { return PearlAwait( ^(void (^setResult)(id)) {
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) { [self resolveAnswerForQuestion:question usingKey:key result:^(NSString *result_) {
setResult( 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; NSString *name = site.name;
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins]; BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
NSString *loginName = site.loginName; NSString *loginName = site.loginName;
id<MPAlgorithm> algorithm = nil; id<MPAlgorithm> algorithm = nil;
if (!name.length) if (!name.length)
err( @"Missing name." ); err( @"Missing name." );
else if (!siteKey) else if (!key)
err( @"Missing key." ); err( @"Missing key." );
else else
algorithm = site.algorithm; algorithm = site.algorithm;
@ -487,244 +458,139 @@ static NSOperationQueue *_mpwQueue = nil;
resultBlock( loginName ); resultBlock( loginName );
else else
PearlNotMainQueue( ^{ 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) { switch (site.type) {
case MPSiteTypeGeneratedMaximum: case MPResultTypeTemplateMaximum:
case MPSiteTypeGeneratedLong: case MPResultTypeTemplateLong:
case MPSiteTypeGeneratedMedium: case MPResultTypeTemplateMedium:
case MPSiteTypeGeneratedBasic: case MPResultTypeTemplateBasic:
case MPSiteTypeGeneratedShort: case MPResultTypeTemplateShort:
case MPSiteTypeGeneratedPIN: case MPResultTypeTemplatePIN:
case MPSiteTypeGeneratedName: case MPResultTypeTemplateName:
case MPSiteTypeGeneratedPhrase: { case MPResultTypeTemplatePhrase: {
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) { if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.", wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
(long)site.type, [site class] ); (long)site.type, [site class] );
break; break;
} }
NSString *name = site.name;
MPSiteType type = site.type;
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter; 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( ^{ PearlNotMainQueue( ^{
resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] ); resultBlock( [algorithm mpwTemplateForSiteNamed:name ofType:type withCounter:counter usingKey:key] );
} ); } );
break; break;
} }
case MPSiteTypeStoredPersonal: { case MPResultTypeStatefulPersonal:
case MPResultTypeStatefulDevice: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) { if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] ); (long)site.type, [site class] );
break; break;
} }
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject; NSDictionary *siteQuery = [self queryForSite:site];
NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
state = state?: ((MPStoredSiteEntity *)site).contentObject;
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] ); resultBlock( [algorithm mpwResultForSiteNamed:name ofType:type parameter:[state encodeBase64]
} ); withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
break; usingKey:key] );
}
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] );
} ); } );
break; 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; NSString *name = site.name;
id<MPAlgorithm> algorithm = nil; id<MPAlgorithm> algorithm = nil;
if (!site.name.length) if (!site.name.length)
err( @"Missing name." ); err( @"Missing name." );
else if (!siteKey) else if (!key)
err( @"Missing key." ); err( @"Missing key." );
else else
algorithm = site.algorithm; algorithm = site.algorithm;
PearlNotMainQueue( ^{ 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 { 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." ); @"Site does not belong to current user." );
NSString *name = question.site.name; NSString *name = question.site.name;
NSString *keyword = question.keyword; NSString *keyword = question.keyword;
id<MPAlgorithm> algorithm = nil; id<MPAlgorithm> algorithm = nil;
if (!name.length) if (!name.length)
err( @"Missing name." ); err( @"Missing name." );
else if (!siteKey) else if (!key)
err( @"Missing key." ); err( @"Missing key." );
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers]) else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
algorithm = question.site.algorithm; algorithm = question.site.algorithm;
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] ); resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:keyword usingKey:key] );
} ); } );
} }
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey - (void)importPassword:(NSString *)cipherText protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
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." );
switch (site.type) { if (cipherText && cipherText.length && site.type & MPResultTypeClassStateful) {
case MPSiteTypeGeneratedMaximum: NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:site.type parameter:cipherText
case MPSiteTypeGeneratedLong: withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
case MPSiteTypeGeneratedMedium: usingKey:importKey];
case MPSiteTypeGeneratedBasic: if (plainText)
case MPSiteTypeGeneratedShort: [self savePassword:plainText toSite:site usingKey:key];
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;
} }
} }
- (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." ); return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:@{
switch (site.type) { (__bridge id)kSecAttrService: site.type & MPSiteFeatureDevicePrivate? @"DevicePrivate": @"Private",
case MPSiteTypeGeneratedMaximum: (__bridge id)kSecAttrAccount: site.name
case MPSiteTypeGeneratedLong: } matches:nil];
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: - (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
break;
}
}
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (!(site.type & MPSiteFeatureExportContent)) if (!(site.type & MPSiteFeatureExportContent))
return nil; return nil;
NSString *result = nil; NSDictionary *siteQuery = [self queryForSite:site];
switch (site.type) { NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
case MPSiteTypeGeneratedMaximum: return [state?: ((MPStoredSiteEntity *)site).contentObject encodeBase64];
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
result = nil;
break;
} }
case MPSiteTypeStoredPersonal: { - (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker {
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: { if (!(type & MPResultTypeClassTemplate))
result = nil;
break;
}
}
return result;
}
- (BOOL)migrateExplicitly:(BOOL)explicit {
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))
return NO; return NO;
size_t count = 0; size_t count = 0;
const char **templates = mpw_templatesForType( type, &count ); const char **templates = mpw_templatesForType( type, &count );

View File

@ -33,7 +33,7 @@
return NO; return NO;
if (!explicit) { if (!explicit) {
if (site.type & MPSiteTypeClassGenerated) { if (site.type & MPResultTypeClassTemplate) {
// This migration requires explicit permission for types of the generated class. // This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES; site.requiresExplicitMigration = YES;
return NO; return NO;

View File

@ -33,7 +33,7 @@
return NO; return NO;
if (!explicit) { 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. // This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES; site.requiresExplicitMigration = YES;
return NO; return NO;

View File

@ -33,7 +33,7 @@
return NO; return NO;
if (!explicit) { if (!explicit) {
if (site.type & MPSiteTypeClassGenerated && if (site.type & MPResultTypeClassTemplate &&
site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) { site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
// This migration requires explicit permission for types of the generated class. // This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES; site.requiresExplicitMigration = YES;

View File

@ -248,9 +248,9 @@
#endif #endif
for (MPSiteEntity *site in user.sites) { for (MPSiteEntity *site in user.sites) {
if (site.type & MPSiteTypeClassStored) { if (site.type & MPResultTypeClassStateful) {
NSString *content; 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. // Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
NSString *masterPassword = nil; NSString *masterPassword = nil;

View File

@ -20,14 +20,6 @@
#import "MPFixable.h" #import "MPFixable.h"
typedef NS_ENUM( NSUInteger, MPImportResult ) {
MPImportResultSuccess,
MPImportResultCancelled,
MPImportResultInvalidPassword,
MPImportResultMalformedInput,
MPImportResultInternalError,
};
@interface MPAppDelegate_Shared(Store) @interface MPAppDelegate_Shared(Store)
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady; + (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. */ /** @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; - (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type; - (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type;
- (MPImportResult)importSites:(NSString *)importedSitesString - (void)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword; askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords; result:(void ( ^ )(NSError *error))resultBlock;
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock;
@end @end

View File

@ -18,6 +18,7 @@
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "mpw-marshall.h" #import "mpw-marshall.h"
#import "mpw-util.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete, #define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
@ -489,7 +490,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return; return;
} }
MPSiteType type = activeUser.defaultType; MPResultType type = activeUser.defaultType;
id<MPAlgorithm> algorithm = MPAlgorithmDefault; id<MPAlgorithm> algorithm = MPAlgorithmDefault;
Class entityType = [algorithm classOfType:type]; 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) if (site.type == type)
return site; return site;
@ -539,328 +540,214 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return site; return site;
} }
- (MPImportResult)importSites:(NSString *)importedSitesString - (void)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword { askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
result:(void ( ^ )(NSError *error))resultBlock {
NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." ); NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
__block MPImportResult result = MPImportResultCancelled;
do { do {
if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword NSError *error = [self importSites:importData askImportPassword:importPassword askUserPassword:userPassword
saveInContext:context]; saveInContext:context];
PearlMainQueue( ^{
resultBlock( error );
} );
}]) }])
break; break;
usleep( (useconds_t)(USEC_PER_SEC * 0.2) ); usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
} while (YES); } while (YES);
return result;
} }
- (MPImportResult)importSites:(NSString *)importedSitesString - (NSError *)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword askUserPassword:(NSString *( ^ )(NSString *userName))askUserPassword
saveInContext:(NSManagedObjectContext *)context { saveInContext:(NSManagedObjectContext *)context {
// Compile patterns. // Read metadata for the import file.
static NSRegularExpression *headerPattern; MPMarshallInfo *info = mpw_marshall_read_info( importData.UTF8String );
static NSArray *sitePatterns; if (info->format == MPMarshallFormatNone)
NSError *error = nil; return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
if (!headerPattern) { @"type" : @(MPMarshallErrorFormat),
headerPattern = [[NSRegularExpression alloc] NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" }]), @"While importing sites." );
options:(NSRegularExpressionOptions)0 error:&error];
if (error) { // Get master password for import file.
MPError( error, @"Error loading the header pattern." ); MPKey *importKey;
return MPImportResultInternalError; NSString *importMasterPassword;
} do {
} importMasterPassword = askImportPassword( @(info->fullName) );
if (!sitePatterns) { if (!importMasterPassword) {
sitePatterns = @[ inf( @"Import cancelled." );
[[NSRegularExpression alloc] // Format 0 mpw_marshal_info_free( info );
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)" return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
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. // Parse import data.
inf( @"Importing sites." ); MPMarshallError importError = { .type = MPMarshallSuccess };
NSUInteger importFormat = 0; MPMarshalledUser *importUser = mpw_marshall_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError );
__block MPUserEntity *user = nil; mpw_marshal_info_free( info );
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;
}
// Header @try {
if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0 if (!importUser || importError.type != MPMarshallSuccess)
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) { return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
err( @"Invalid header format in line: %@", importedSiteLine ); @"type" : @(importError.type),
return MPImportResultMalformedInput; NSLocalizedDescriptionKey: @(importError.description),
} }]), @"While importing sites." );
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;
// Find an existing user to update.
NSError *error = nil;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", @(importUser->fullName)];
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error]; NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users) { if (!users)
MPError( error, @"While looking for user: %@.", importUserName ); return MPError( error, @"While looking for user: %@.", @(importUser->fullName) );
return MPImportResultInternalError; if ([users count] > 1)
} return MPMakeError( @"While looking for user: %@, found more than one: %zu",
if ([users count] > 1) { @(importUser->fullName), (size_t)[users count] );
err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] );
return MPImportResultInternalError;
}
user = [users lastObject]; // Get master key for user.
dbg( @"Existing user? %@", [user debugDescription] ); MPUserEntity *user = [users lastObject];
} MPKey *userKey = importKey;
if ([headerName isEqualToString:@"Avatar"]) while (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) {
importAvatar = (NSUInteger)[headerValue integerValue]; NSString *userMasterPassword = askUserPassword( user.name );
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;
}
// 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];
}
}
[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) { if (!userMasterPassword) {
inf( @"Import cancelled." ); inf( @"Import cancelled." );
return MPImportResultCancelled; return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
} }
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. userKey = [[MPKey alloc] initForFullName:@(importUser->fullName) withMasterPassword:userMasterPassword];
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. // Update or create user.
if (user) { if (!user) {
if (importAvatar != NSNotFound) user = [MPUserEntity insertNewObjectInContext:context];
user.avatar = importAvatar; user.name = @(importUser->fullName);
if (importDefaultType) }
user.defaultType = importDefaultType; user.algorithm = MPAlgorithmForVersion( importUser->algorithm );
dbg( @"Updating User: %@", [user debugDescription] ); user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
user.avatar = importUser->avatar;
user.defaultType = importUser->defaultType;
user.lastUsed = [NSDate dateWithTimeIntervalSince1970:MAX( user.lastUsed.timeIntervalSince1970, importUser->lastUsed )];
dbg( @"Importing user: %@", [user debugDescription] );
// Update or create sites.
for (size_t s = 0; s < importUser->sites_count; ++s) {
MPMarshalledSite *importSite = &importUser->sites[s];
// 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 { else {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = importUserName;
user.algorithm = MPAlgorithmDefault;
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
user.defaultType = importDefaultType?: user.algorithm.defaultType;
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created 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];
// Create new site. // Create new site.
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version ); id<MPAlgorithm> algorithm = MPAlgorithmForVersion( importSite->algorithm );
Class entityType = [algorithm classOfType:type]; Class entityType = [algorithm classOfType:importSite->type];
if (!entityType) { if (!entityType)
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type ); return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->name), (long)importSite->type );
return MPImportResultInternalError;
}
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] ); MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.user = user;
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
dbg( @"Created site: %@", [site debugDescription] );
}
} }
if (![context saveToStore]) if (![context saveToStore])
return MPImportResultInternalError; return MPMakeError( @"Failed saving imported changes." );
inf( @"Import completed successfully." ); inf( @"Import completed successfully." );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{ [[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey: user MPSitesImportedNotificationUserKey: user
}]; }];
return nil;
return MPImportResultSuccess; }
@finally {
mpw_marshal_free( importUser );
}
} }
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords { - (void)importSite:(const MPMarshalledSite *)importSite protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site
usingKey:(MPKey *)userKey {
MPUserEntity *activeUser = [self activeUserForMainThread]; site.name = @(importSite->name);
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID ); 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, - (void)exportSitesRevealPasswords:(BOOL)revealPasswords
[self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version ); askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
exportUser.avatar = activeUser.avatar; result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock {
exportUser.defaultType = activeUser.defaultType;
exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970;
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user = [self activeUserInContext:context];
NSString *masterPassword = askImportPassword( user.name );
for (MPSiteEntity *site in activeUser.sites) { inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
MPMarshalledSite exportSite = mpw_marshall_site( &exportUser, MPMarshalledUser *exportUser = mpw_marshall_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version );
site.name.UTF8String, site.type, site.counter, site.algorithm.version ); exportUser->redacted = !revealPasswords;
exportSite.loginName = site.loginName.UTF8String; exportUser->avatar = (unsigned int)user.avatar;
exportSite.url = site.url.UTF8String; exportUser->defaultType = user.defaultType;
exportSite.uses = site.uses; exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970;
exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
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];
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) for (MPSiteQuestionEntity *siteQuestion in site.questions)
mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String ); mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
} }
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser ); 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 @end

View File

@ -48,7 +48,7 @@
@interface MPSiteEntity(MP)<MPFixable> @interface MPSiteEntity(MP)<MPFixable>
@property(assign) BOOL loginGenerated; @property(assign) BOOL loginGenerated;
@property(assign) MPSiteType type; @property(assign) MPResultType type;
@property(readonly) NSString *typeName; @property(readonly) NSString *typeName;
@property(readonly) NSString *typeShortName; @property(readonly) NSString *typeShortName;
@property(readonly) NSString *typeClassName; @property(readonly) NSString *typeClassName;
@ -71,7 +71,7 @@
@interface MPGeneratedSiteEntity(MP) @interface MPGeneratedSiteEntity(MP)
@property(assign) NSUInteger counter; @property(assign) MPCounterValue counter;
@end @end
@ -80,7 +80,7 @@
@property(assign) NSUInteger avatar; @property(assign) NSUInteger avatar;
@property(assign) BOOL saveKey; @property(assign) BOOL saveKey;
@property(assign) BOOL touchID; @property(assign) BOOL touchID;
@property(assign) MPSiteType defaultType; @property(assign) MPResultType defaultType;
@property(readonly) NSString *userID; @property(readonly) NSString *userID;
@property(strong) id<MPAlgorithm> algorithm; @property(strong) id<MPAlgorithm> algorithm;

View File

@ -82,9 +82,9 @@
return MPFixableResultNoProblems; return MPFixableResultNoProblems;
} }
- (MPSiteType)type { - (MPResultType)type {
return (MPSiteType)[self.type_ unsignedIntegerValue]; return (MPResultType)[self.type_ unsignedIntegerValue];
} }
- (void)setLoginGenerated:(BOOL)aLoginGenerated { - (void)setLoginGenerated:(BOOL)aLoginGenerated {
@ -97,7 +97,7 @@
return [self.loginGenerated_ boolValue]; return [self.loginGenerated_ boolValue];
} }
- (void)setType:(MPSiteType)aType { - (void)setType:(MPResultType)aType {
self.type_ = @(aType); self.type_ = @(aType);
} }
@ -251,7 +251,7 @@
MPFixableResult result = [super findAndFixInconsistenciesInContext:context]; 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 // Invalid self.type
result = MPApplyFix( result, ^MPFixableResult { result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.", wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
@ -259,7 +259,7 @@
self.type = self.user.defaultType; self.type = self.user.defaultType;
return MPFixableResultProblemsFixed; 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 // Invalid self.user.defaultType
result = MPApplyFix( result, ^MPFixableResult { result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.", wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
@ -270,7 +270,7 @@
if (![self isKindOfClass:[self.algorithm classOfType:self.type]]) if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
// Mismatch between self.type and self.class // Mismatch between self.type and self.class
result = MPApplyFix( result, ^MPFixableResult { 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]]) { if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.", wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
self.name, self.user.name, (long)self.type, self.class, (long)newType ); self.name, self.user.name, (long)self.type, self.class, (long)newType );
@ -286,12 +286,12 @@
return result; 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); self.counter_ = @(aCounter);
} }
@ -354,12 +354,12 @@
self.touchID_ = @(aTouchID); 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); self.defaultType_ = @(aDefaultType);
} }

View File

@ -22,6 +22,7 @@
__BEGIN_DECLS __BEGIN_DECLS
extern NSString *const MPErrorDomain; extern NSString *const MPErrorDomain;
extern NSInteger const MPErrorHangCode; extern NSInteger const MPErrorHangCode;
extern NSInteger const MPErrorMarshallCode;
extern NSString *const MPSignedInNotification; extern NSString *const MPSignedInNotification;
extern NSString *const MPSignedOutNotification; extern NSString *const MPSignedOutNotification;
@ -38,13 +39,20 @@ __END_DECLS
#ifdef CRASHLYTICS #ifdef CRASHLYTICS
#define MPError(error_, message, ...) ({ \ #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]) { \ if ([[MPConfig get].sendInfo boolValue]) { \
[[Crashlytics sharedInstance] recordError:error_ withAdditionalUserInfo:@{ \ [[Crashlytics sharedInstance] recordError:error withAdditionalUserInfo:@{ \
@"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \ @"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 #else
#define MPError(error_, message, ...) ({ \ #define MPError(error_, message, ...) ({ \

View File

@ -20,6 +20,7 @@
NSString *const MPErrorDomain = @"MPErrorDomain"; NSString *const MPErrorDomain = @"MPErrorDomain";
NSInteger const MPErrorHangCode = 1; NSInteger const MPErrorHangCode = 1;
NSInteger const MPErrorMarshallCode = 1;
NSString *const MPSignedInNotification = @"MPSignedInNotification"; NSString *const MPSignedInNotification = @"MPSignedInNotification";
NSString *const MPSignedOutNotification = @"MPSignedOutNotification"; NSString *const MPSignedOutNotification = @"MPSignedOutNotification";

View File

@ -233,13 +233,13 @@
else else
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
[self updatePasswordWithResult: [self updatePasswordWithResult:
[self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter [self.algorithm mpwTemplateForSiteNamed:self.name ofType:self.type withCounter:self.counter
usingKey:[MPAppDelegate_Shared get].key]]; usingKey:[MPAppDelegate_Shared get].key]];
[self updateLoginNameWithResult: [self updateLoginNameWithResult:
[self.algorithm generateLoginForSiteNamed:self.name [self.algorithm mpwLoginForSiteNamed:self.name
usingKey:[MPAppDelegate_Shared get].key]]; usingKey:[MPAppDelegate_Shared get].key]];
[self updateAnswerWithResult: [self updateAnswerWithResult:
[self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question [self.algorithm mpwAnswerForSiteNamed:self.name onQuestion:self.question
usingKey:[MPAppDelegate_Shared get].key]]; usingKey:[MPAppDelegate_Shared get].key]];
} ); } );
} }
@ -252,7 +252,7 @@
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { [entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
[self updateLoginNameWithResult:result]; [self updateLoginNameWithResult:result];
}]; }];
[self updateAnswerWithResult:[self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question [self updateAnswerWithResult:[self.algorithm mpwAnswerForSiteNamed:self.name onQuestion:self.question
usingKey:[MPAppDelegate_Shared get].key]]; usingKey:[MPAppDelegate_Shared get].key]];
} }

View File

@ -376,7 +376,7 @@
MPSiteType type = (MPSiteType)[types[t] unsignedIntegerValue]; MPSiteType type = (MPSiteType)[types[t] unsignedIntegerValue];
NSString *title = [site.algorithm nameOfType:type]; NSString *title = [site.algorithm nameOfType:type];
if (type & MPSiteTypeClassGenerated) if (type & MPSiteTypeClassGenerated)
title = strf( @"%@ %@", [site.algorithm generatePasswordForSiteNamed:site.name ofType:type withCounter:site.counter title = strf( @"%@ %@", [site.algorithm mpwTemplateForSiteNamed:site.name ofType:type withCounter:site.counter
usingKey:[MPMacAppDelegate get].key], title ); usingKey:[MPMacAppDelegate get].key], title );
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0]; NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];

View File

@ -137,7 +137,7 @@
- (void)updatePassword { - (void)updatePassword {
NSString *siteName = self.siteField.text; NSString *siteName = self.siteField.text;
MPSiteType siteType = [self siteType]; MPResultType siteType = [self siteType];
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value; NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter ); self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
@ -147,7 +147,7 @@
[self.emergencyPasswordQueue addOperationWithBlock:^{ [self.emergencyPasswordQueue addOperationWithBlock:^{
NSString *sitePassword = nil; NSString *sitePassword = nil;
if (self.key && [siteName length]) if (self.key && [siteName length])
sitePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:siteName ofType:siteType withCounter:siteCounter sitePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:siteName ofType:siteType withCounter:siteCounter
usingKey:self.key]; usingKey:self.key];
PearlMainQueue( ^{ PearlMainQueue( ^{
@ -157,21 +157,21 @@
}]; }];
} }
- (enum MPSiteType)siteType { - (MPResultType)siteType {
switch (self.typeControl.selectedSegmentIndex) { switch (self.typeControl.selectedSegmentIndex) {
case 0: case 0:
return MPSiteTypeGeneratedMaximum; return MPResultTypeTemplateMaximum;
case 1: case 1:
return MPSiteTypeGeneratedLong; return MPResultTypeTemplateLong;
case 2: case 2:
return MPSiteTypeGeneratedMedium; return MPResultTypeTemplateMedium;
case 3: case 3:
return MPSiteTypeGeneratedBasic; return MPResultTypeTemplateBasic;
case 4: case 4:
return MPSiteTypeGeneratedShort; return MPResultTypeTemplateShort;
case 5: case 5:
return MPSiteTypeGeneratedPIN; return MPResultTypeTemplatePIN;
default: default:
Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex ); Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex );
} }

View File

@ -60,14 +60,14 @@
self.touchIDSwitch.on = activeUser.touchID; self.touchIDSwitch.on = activeUser.touchID;
self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID]; 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.generated1TypeControl.selectedSegmentIndex = [self generated1SegmentIndexForType:defaultType];
self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType]; self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType];
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType]; self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType];
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
NSString *examplePassword = nil; NSString *examplePassword = nil;
if (defaultType & MPSiteTypeClassGenerated) if (defaultType & MPResultTypeClassTemplate)
examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType examplePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:@"test" ofType:defaultType
withCounter:1 usingKey:[MPiOSAppDelegate get].key]; withCounter:1 usingKey:[MPiOSAppDelegate get].key];
PearlMainQueue( ^{ PearlMainQueue( ^{
self.typeSamplePassword.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil; self.typeSamplePassword.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
@ -164,7 +164,7 @@
if (sender != self.storedTypeControl) if (sender != self.storedTypeControl)
self.storedTypeControl.selectedSegmentIndex = -1; self.storedTypeControl.selectedSegmentIndex = -1;
MPSiteType defaultType = [self typeForSelectedSegment]; MPResultType defaultType = [self typeForSelectedSegment];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType = defaultType; [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = defaultType;
[context saveToStore]; [context saveToStore];
@ -242,7 +242,7 @@
return nil; return nil;
} }
- (MPSiteType)typeForSelectedSegment { - (MPResultType)typeForSelectedSegment {
NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex; NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex;
NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex; NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex;
@ -250,30 +250,30 @@
switch (selectedGenerated1Index) { switch (selectedGenerated1Index) {
case 0: case 0:
return MPSiteTypeGeneratedPhrase; return MPResultTypeTemplatePhrase;
case 1: case 1:
return MPSiteTypeGeneratedName; return MPResultTypeTemplateName;
default: default:
switch (selectedGenerated2Index) { switch (selectedGenerated2Index) {
case 0: case 0:
return MPSiteTypeGeneratedMaximum; return MPResultTypeTemplateMaximum;
case 1: case 1:
return MPSiteTypeGeneratedLong; return MPResultTypeTemplateLong;
case 2: case 2:
return MPSiteTypeGeneratedMedium; return MPResultTypeTemplateMedium;
case 3: case 3:
return MPSiteTypeGeneratedBasic; return MPResultTypeTemplateBasic;
case 4: case 4:
return MPSiteTypeGeneratedShort; return MPResultTypeTemplateShort;
case 5: case 5:
return MPSiteTypeGeneratedPIN; return MPResultTypeTemplatePIN;
default: default:
switch (selectedStoredIndex) { switch (selectedStoredIndex) {
case 0: case 0:
return MPSiteTypeStoredPersonal; return MPResultTypeStatefulPersonal;
case 1: case 1:
return MPSiteTypeStoredDevicePrivate; return MPResultTypeStatefulDevice;
default: default:
Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld", Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
(long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex ); (long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
@ -282,44 +282,44 @@
} }
} }
- (NSInteger)generated1SegmentIndexForType:(MPSiteType)type { - (NSInteger)generated1SegmentIndexForType:(MPResultType)type {
switch (type) { switch (type) {
case MPSiteTypeGeneratedPhrase: case MPResultTypeTemplatePhrase:
return 0; return 0;
case MPSiteTypeGeneratedName: case MPResultTypeTemplateName:
return 1; return 1;
default: default:
return -1; return -1;
} }
} }
- (NSInteger)generated2SegmentIndexForType:(MPSiteType)type { - (NSInteger)generated2SegmentIndexForType:(MPResultType)type {
switch (type) { switch (type) {
case MPSiteTypeGeneratedMaximum: case MPResultTypeTemplateMaximum:
return 0; return 0;
case MPSiteTypeGeneratedLong: case MPResultTypeTemplateLong:
return 1; return 1;
case MPSiteTypeGeneratedMedium: case MPResultTypeTemplateMedium:
return 2; return 2;
case MPSiteTypeGeneratedBasic: case MPResultTypeTemplateBasic:
return 3; return 3;
case MPSiteTypeGeneratedShort: case MPResultTypeTemplateShort:
return 4; return 4;
case MPSiteTypeGeneratedPIN: case MPResultTypeTemplatePIN:
return 5; return 5;
default: default:
return -1; return -1;
} }
} }
- (NSInteger)storedSegmentIndexForType:(MPSiteType)type { - (NSInteger)storedSegmentIndexForType:(MPResultType)type {
switch (type) { switch (type) {
case MPSiteTypeStoredPersonal: case MPResultTypeStatefulPersonal:
return 0; return 0;
case MPSiteTypeStoredDevicePrivate: case MPResultTypeStatefulDevice:
return 1; return 1;
default: default:
return -1; return -1;

View File

@ -284,7 +284,7 @@
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic [PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
initSheet:^(UIActionSheet *sheet) { initSheet:^(UIActionSheet *sheet) {
for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) { for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) {
MPSiteType type = (MPSiteType)[typeNumber unsignedIntegerValue]; MPResultType type = (MPResultType)[typeNumber unsignedIntegerValue];
NSString *typeName = [mainSite.algorithm nameOfType:type]; NSString *typeName = [mainSite.algorithm nameOfType:type];
if (type == mainSite.type) if (type == mainSite.type)
[sheet addButtonWithTitle:strf( @"● %@", typeName )]; [sheet addButtonWithTitle:strf( @"● %@", typeName )];
@ -295,7 +295,7 @@
if (buttonIndex == [sheet cancelButtonIndex]) if (buttonIndex == [sheet cancelButtonIndex])
return; return;
MPSiteType type = (MPSiteType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?: MPResultType type = (MPResultType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?:
mainSite.user.defaultType?: mainSite.algorithm.defaultType; mainSite.user.defaultType?: mainSite.algorithm.defaultType;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
@ -311,7 +311,7 @@
self.loginNameField.enabled = YES; self.loginNameField.enabled = YES;
self.passwordField.enabled = YES; self.passwordField.enabled = YES;
if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPSiteTypeClassStored) if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPResultTypeClassStateful)
[self.passwordField becomeFirstResponder]; [self.passwordField becomeFirstResponder];
else else
[self.loginNameField becomeFirstResponder]; [self.loginNameField becomeFirstResponder];
@ -537,7 +537,7 @@
self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]; self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
self.modeButton.visible = !self.transientSite; self.modeButton.visible = !self.transientSite;
self.modeButton.alpha = settingsMode? 0.5f: 0.1f; 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.modeButton.selected = settingsMode;
self.strengthLabel.gone = !settingsMode; self.strengthLabel.gone = !settingsMode;
self.modeScrollView.scrollEnabled = !self.transientSite; self.modeScrollView.scrollEnabled = !self.transientSite;
@ -565,8 +565,8 @@
// Site Password // Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue]; self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
self.passwordField.attributedPlaceholder = stra( self.passwordField.attributedPlaceholder = stra(
mainSite.type & MPSiteTypeClassStored? strl( @"No password" ): mainSite.type & MPResultTypeClassStateful? strl( @"No password" ):
mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{ mainSite.type & MPResultTypeClassTemplate? strl( @"..." ): @"", @{
NSForegroundColorAttributeName: [UIColor whiteColor] NSForegroundColorAttributeName: [UIColor whiteColor]
} ); } );
@ -585,9 +585,9 @@
BOOL loginGenerated = site.loginGenerated; BOOL loginGenerated = site.loginGenerated;
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key]; NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType; MPResultType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
if (self.transientSite && transientType & MPSiteTypeClassGenerated) if (self.transientSite && transientType & MPResultTypeClassTemplate)
password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:transientType password = [MPAlgorithmDefault mpwTemplateForSiteNamed:self.transientSite ofType:transientType
withCounter:1 usingKey:key]; withCounter:1 usingKey:key];
else if (site) else if (site)
password = [site resolvePasswordUsingKey:key]; password = [site resolvePasswordUsingKey:key];

View File

@ -23,8 +23,8 @@
@protocol MPTypeDelegate<NSObject> @protocol MPTypeDelegate<NSObject>
@required @required
- (void)didSelectType:(MPSiteType)type; - (void)didSelectType:(MPResultType)type;
- (MPSiteType)selectedType; - (MPResultType)selectedType;
@optional @optional
- (MPSiteEntity *)selectedSite; - (MPSiteEntity *)selectedSite;

View File

@ -22,7 +22,7 @@
@interface MPTypeViewController() @interface MPTypeViewController()
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath; - (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath;
@end @end
@ -75,11 +75,11 @@
if ([self.delegate respondsToSelector:@selector( selectedSite )]) if ([self.delegate respondsToSelector:@selector( selectedSite )])
selectedSite = [self.delegate selectedSite]; selectedSite = [self.delegate selectedSite];
MPSiteType cellType = [self typeAtIndexPath:indexPath]; MPResultType cellType = [self typeAtIndexPath:indexPath];
MPSiteType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType]; MPResultType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType];
cell.selected = (selectedType == cellType); cell.selected = (selectedType == cellType);
if (cellType != (MPSiteType)NSNotFound && cellType & MPSiteTypeClassGenerated) { if (cellType != (MPResultType)NSNotFound && cellType & MPResultTypeClassTemplate) {
[(UITextField *)[cell viewWithTag:2] setText:@"..."]; [(UITextField *)[cell viewWithTag:2] setText:@"..."];
NSString *name = selectedSite.name; NSString *name = selectedSite.name;
@ -88,7 +88,7 @@
counter = ((MPGeneratedSiteEntity *)selectedSite).counter; counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType NSString *typeContent = [MPAlgorithmDefault mpwTemplateForSiteNamed:name ofType:cellType
withCounter:counter usingKey:[MPiOSAppDelegate get].key]; withCounter:counter usingKey:[MPiOSAppDelegate get].key];
PearlMainQueue( ^{ PearlMainQueue( ^{
@ -104,8 +104,8 @@
NSAssert( self.navigationController.topViewController == self, @"Not the currently active navigation item." ); NSAssert( self.navigationController.topViewController == self, @"Not the currently active navigation item." );
MPSiteType type = [self typeAtIndexPath:indexPath]; MPResultType type = [self typeAtIndexPath:indexPath];
if (type == (MPSiteType)NSNotFound) if (type == (MPResultType)NSNotFound)
// Selected a non-type row. // Selected a non-type row.
return; return;
@ -113,28 +113,28 @@
[self.navigationController popViewControllerAnimated:YES]; [self.navigationController popViewControllerAnimated:YES];
} }
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath { - (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) { switch (indexPath.section) {
case 0: { case 0: {
// Generated // Generated
switch (indexPath.row) { switch (indexPath.row) {
case 0: case 0:
return (MPSiteType)NSNotFound; return (MPResultType)NSNotFound;
case 1: case 1:
return MPSiteTypeGeneratedMaximum; return MPResultTypeTemplateMaximum;
case 2: case 2:
return MPSiteTypeGeneratedLong; return MPResultTypeTemplateLong;
case 3: case 3:
return MPSiteTypeGeneratedMedium; return MPResultTypeTemplateMedium;
case 4: case 4:
return MPSiteTypeGeneratedBasic; return MPResultTypeTemplateBasic;
case 5: case 5:
return MPSiteTypeGeneratedShort; return MPResultTypeTemplateShort;
case 6: case 6:
return MPSiteTypeGeneratedPIN; return MPResultTypeTemplatePIN;
case 7: case 7:
return (MPSiteType)NSNotFound; return (MPResultType)NSNotFound;
default: { default: {
Throw( @"Unsupported row: %ld, when selecting generated site type.", (long)indexPath.row ); Throw( @"Unsupported row: %ld, when selecting generated site type.", (long)indexPath.row );
@ -146,13 +146,13 @@
// Stored // Stored
switch (indexPath.row) { switch (indexPath.row) {
case 0: case 0:
return (MPSiteType)NSNotFound; return (MPResultType)NSNotFound;
case 1: case 1:
return MPSiteTypeStoredPersonal; return MPResultTypeStatefulPersonal;
case 2: case 2:
return MPSiteTypeStoredDevicePrivate; return MPResultTypeStatefulDevice;
case 3: case 3:
return (MPSiteType)NSNotFound; return (MPResultType)NSNotFound;
default: { default: {
Throw( @"Unsupported row: %ld, when selecting stored site type.", (long)indexPath.row ); Throw( @"Unsupported row: %ld, when selecting stored site type.", (long)indexPath.row );

View File

@ -20,6 +20,7 @@
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPStoreViewController.h" #import "MPStoreViewController.h"
#import "mpw-marshall.h"
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate> @interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@ -177,62 +178,46 @@
return YES; return YES;
} }
- (void)importSites:(NSString *)importedSitesString { - (void)importSites:(NSString *)importData {
if ([NSThread isMainThread]) { if ([NSThread isMainThread]) {
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
[self importSites:importedSitesString]; [self importSites:importData];
} ); } );
return; return;
} }
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"]; 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)) { return PearlAwait( ^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:@"Import File's Master Password" [PearlAlert showAlertWithTitle:strf( @"Importing Sites For\n%@", userName )
message:strf( @"%@'s export was done using a different master password.\n" message:@"Enter the master password used to create this export file."
@"Enter that master password to unlock the exported data.", userName )
viewStyle:UIAlertViewStyleSecureTextInput viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ cancelButtonIndex]) if (buttonIndex_ == [alert_ cancelButtonIndex])
setResult( nil ); setResult( nil );
else else
setResult( [alert_ textFieldAtIndex:0].text ); setResult( [alert_ textFieldAtIndex:0].text );
} } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} ); } );
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) { } askUserPassword:^NSString *(NSString *userName) {
return PearlAwait( (id)^(void (^setResult)(id)) { return PearlAwait( (id)^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName ) [PearlAlert showAlertWithTitle:strf( @"Master Password For\n%@", userName )
message:strf( @"Imports %lu sites, overwriting %lu.", message:@"Enter the current master password for this user."
(unsigned long)importCount, (unsigned long)deleteCount )
viewStyle:UIAlertViewStyleSecureTextInput viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ cancelButtonIndex]) if (buttonIndex_ == [alert_ cancelButtonIndex])
setResult( nil ); setResult( nil );
else else
setResult( [alert_ textFieldAtIndex:0].text ); setResult( [alert_ textFieldAtIndex:0].text );
} } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
} ); } );
}]; } result:^(NSError *error) {
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]; [activityOverlay cancelOverlayAnimated:YES];
if (error && !(error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError))
[PearlAlert showError:error.localizedDescription];
}];
} }
- (void)applicationWillEnterForeground:(UIApplication *)application { - (void)applicationWillEnterForeground:(UIApplication *)application {
@ -250,10 +235,9 @@
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
NSString *importHeader = @"# Master Password site export"; NSString *importData = [UIPasteboard generalPasteboard].string;
NSString *importedSitesString = [UIPasteboard generalPasteboard].string; MPMarshallInfo *importInfo = mpw_marshall_read_info( importData.UTF8String );
if ([importedSitesString length] > [importHeader length] && if (importInfo->format != MPMarshallFormatNone)
[[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader])
[PearlAlert showAlertWithTitle:@"Import Sites?" message: [PearlAlert showAlertWithTitle:@"Import Sites?" message:
@"We've detected Master Password import sites on your pasteboard, would you like to import them?" @"We've detected Master Password import sites on your pasteboard, would you like to import them?"
viewStyle:UIAlertViewStyleDefault initAlert:nil viewStyle:UIAlertViewStyleDefault initAlert:nil
@ -261,9 +245,10 @@
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; return;
[self importSites:importedSitesString]; [self importSites:importData];
[UIPasteboard generalPasteboard].string = @""; [UIPasteboard generalPasteboard].string = @"";
} cancelTitle:@"No" otherTitles:@"Import Sites", nil]; } cancelTitle:@"No" otherTitles:@"Import Sites", nil];
mpw_marshal_info_free( importInfo );
} ); } );
[super applicationDidBecomeActive:application]; [super applicationDidBecomeActive:application];
@ -449,9 +434,43 @@
return; return;
} }
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords]; [self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
NSString *message; 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;
}
[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"];
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
if (buttonIndex == [sheet firstOtherButtonIndex]) {
NSString *message;
if (revealPasswords) if (revealPasswords)
message = strf( @"Export of Master Password sites with passwords included.\n\n" 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" @"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@ -470,20 +489,9 @@
[PearlInfoPlist get].CFBundleShortVersionString, [PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion ); [PearlInfoPlist get].CFBundleVersion );
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
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]) {
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc] attachments:[[PearlEMailAttachment alloc]
initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] initWithContent:[mpsites dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain" fileName:exportFileName], mimeType:@"text/plain" fileName:exportFileName],
nil]; nil];
return; return;
@ -494,10 +502,10 @@
NSURL *exportURL = [[applicationSupportURL NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES] URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO]; URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *error = nil; NSError *writeError = nil;
if (![[exportedSites dataUsingEncoding:NSUTF8StringEncoding] if (![[mpsites dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&error]) writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
MPError( error, @"Failed to write export data to URL %@.", exportURL ); MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
else { else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL]; self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites"; self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
@ -505,6 +513,7 @@
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES]; [self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
} }
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil]; } cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil];
}];
} }
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset { - (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {

View File

@ -21,6 +21,7 @@
#endif #endif
#define MP_ENV_fullName "MP_FULLNAME" #define MP_ENV_fullName "MP_FULLNAME"
#define MP_ENV_algorithm "MP_ALGORITHM" #define MP_ENV_algorithm "MP_ALGORITHM"
#define MP_ENV_format "MP_FORMAT"
static void usage() { 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|F format The mpsites format to use for reading/writing site parameters.\n"
" -F forces the use of the given format,\n" " -F forces the use of the given format,\n"
" -f allows fallback/migration.\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" " f, flat | ~/.mpw.d/Full Name.%s\n"
" j, json | ~/.mpw.d/Full Name.%s\n\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( "" inf( ""
" -R redacted Whether to save the mpsites in redacted format or not.\n" " -R redacted Whether to save the mpsites in redacted format or not.\n"
" Defaults to 1, redacted.\n\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; const char *keyPurposeArg = NULL, *keyContextArg = NULL, *sitesFormatArg = NULL, *sitesRedactedArg = NULL;
fullNameArg = mpw_getenv( MP_ENV_fullName ); fullNameArg = mpw_getenv( MP_ENV_fullName );
algorithmVersionArg = mpw_getenv( MP_ENV_algorithm ); algorithmVersionArg = mpw_getenv( MP_ENV_algorithm );
sitesFormatArg = mpw_getenv( MP_ENV_format );
// Read the command-line options. // 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;) 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 ) ); char *sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) );
if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) { if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) {
dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
free( sitesPath );
// Try to fall back to the flat format. // Try to fall back to the flat format.
if (!sitesFormatFixed) { if (!sitesFormatFixed) {
free( sitesPath );
sitesPath = mpw_path( fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) );
if (sitesPath && (sitesFile = fopen( sitesPath, "r" )))
sitesFormat = MPMarshallFormatFlat; sitesFormat = MPMarshallFormatFlat;
sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) ); else
if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" )))
dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); 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 { else {
// Read file. // Read file.
size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0; size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0;
char *buf = NULL; char *sitesInputData = NULL;
while ((mpw_realloc( &buf, &bufSize, readAmount )) && while ((mpw_realloc( &sitesInputData, &bufSize, readAmount )) &&
(bufOffset += (readSize = fread( buf + bufOffset, 1, readAmount, sitesFile ))) && (bufOffset += (readSize = fread( sitesInputData + bufOffset, 1, readAmount, sitesFile ))) &&
(readSize == readAmount)); (readSize == readAmount));
if (ferror( sitesFile )) if (ferror( sitesFile ))
wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, 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. // Parse file.
MPMarshallError marshallError = { .type = MPMarshallSuccess }; 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) { if (marshallError.type == MPMarshallErrorMasterPassword) {
// Incorrect master password. // Incorrect master password.
if (!allowPasswordUpdate) { if (!allowPasswordUpdate) {
ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description ); ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description );
mpw_marshal_free( user ); mpw_marshal_free( user );
mpw_free( buf, bufSize ); mpw_free( sitesInputData, bufSize );
free( sitesPath ); free( sitesPath );
return EX_DATAERR; return EX_DATAERR;
} }
@ -321,14 +326,14 @@ int main(int argc, char *const argv[]) {
importMasterPassword = mpw_getpass( "Old master password: " ); importMasterPassword = mpw_getpass( "Old master password: " );
mpw_marshal_free( user ); mpw_marshal_free( user );
user = mpw_marshall_read( buf, sitesFormat, importMasterPassword, &marshallError ); user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError );
} }
if (user) { if (user) {
mpw_free_string( user->masterPassword ); mpw_free_string( user->masterPassword );
user->masterPassword = strdup( masterPassword ); user->masterPassword = strdup( masterPassword );
} }
} }
mpw_free( buf, bufSize ); mpw_free( sitesInputData, bufSize );
if (!user || marshallError.type != MPMarshallSuccess) { if (!user || marshallError.type != MPMarshallSuccess) {
err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description ); err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description );
mpw_marshal_free( user ); mpw_marshal_free( user );
@ -471,7 +476,7 @@ int main(int argc, char *const argv[]) {
if (keyPurpose == MPKeyPurposeIdentification && site && !site->loginGenerated && site->loginName) if (keyPurpose == MPKeyPurposeIdentification && site && !site->loginGenerated && site->loginName)
fprintf( stdout, "%s\n", 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 ); mpw_free_string( site->content );
if (!(site->content = mpw_siteState( masterKey, siteName, siteCounter, if (!(site->content = mpw_siteState( masterKey, siteName, siteCounter,
keyPurpose, keyContext, resultType, resultParam, algorithmVersion ))) { keyPurpose, keyContext, resultType, resultParam, algorithmVersion ))) {
@ -483,7 +488,7 @@ int main(int argc, char *const argv[]) {
inf( "saved.\n" ); inf( "saved.\n" );
} }
else { else {
if (!resultParam && site && site->content && resultType & MPResultTypeClassState) if (!resultParam && site && site->content && resultType & MPResultTypeClassStateful)
resultParam = strdup( site->content ); resultParam = strdup( site->content );
const char *siteResult = mpw_siteResult( masterKey, siteName, siteCounter, const char *siteResult = mpw_siteResult( masterKey, siteName, siteCounter,
keyPurpose, keyContext, resultType, resultParam, algorithmVersion ); keyPurpose, keyContext, resultType, resultParam, algorithmVersion );