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;
}
}
else if (resultType & MPResultTypeClassState) {
else if (resultType & MPResultTypeClassStateful) {
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam );

View File

@ -21,7 +21,7 @@
#include "mpw-marshall-util.h"
#include "mpw-util.h"
char *mpw_get_token(char **in, char *eol, char *delim) {
char *mpw_get_token(const char **in, const char *eol, char *delim) {
// Skip leading spaces.
for (; **in == ' '; ++*in);

View File

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

View File

@ -89,6 +89,20 @@ MPMarshalledQuestion *mpw_marshal_question(
return question;
}
bool mpw_marshal_info_free(
MPMarshallInfo *info) {
if (!info)
return true;
bool success = true;
success &= mpw_free_string( info->fullName );
success &= mpw_free_string( info->keyID );
success &= mpw_free( info, sizeof( MPMarshallInfo ) );
return success;
}
bool mpw_marshal_free(
MPMarshalledUser *user) {
@ -313,6 +327,9 @@ bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) {
switch (outFormat) {
case MPMarshallFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return false;
case MPMarshallFormatFlat:
return mpw_marshall_write_flat( out, user, error );
case MPMarshallFormatJSON:
@ -323,10 +340,63 @@ bool mpw_marshall_write(
}
}
static void mpw_marshall_read_flat_info(
const char *in, MPMarshallInfo *info) {
info->algorithm = MPAlgorithmVersionCurrent;
// Parse import data.
bool headerStarted = false;
for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
// Comment or header
if (*positionInLine == '#') {
++positionInLine;
if (!headerStarted) {
if (*positionInLine == '#')
// ## starts header
headerStarted = true;
// Comment before header
continue;
}
if (*positionInLine == '#')
// ## ends header
break;
// Header
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue)
continue;
if (strcmp( headerName, "Algorithm" ) == 0)
info->algorithm = (MPAlgorithmVersion)atoi( headerValue );
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
info->fullName = strdup( headerValue );
if (strcmp( headerName, "Key ID" ) == 0)
info->keyID = strdup( headerValue );
if (strcmp( headerName, "Passwords" ) == 0)
info->redacted = strcmp( headerValue, "VISIBLE" ) != 0;
if (strcmp( headerName, "Date" ) == 0)
info->date = mpw_mktime( headerValue );
mpw_free_string( headerName );
mpw_free_string( headerValue );
continue;
}
}
}
static MPMarshalledUser *mpw_marshall_read_flat(
char *in, const char *masterPassword, MPMarshallError *error) {
const char *in, const char *masterPassword, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) {
error->type = MPMarshallErrorStructure;
error->description = mpw_str( "No input data." );
return NULL;
}
// Parse import data.
MPMasterKey masterKey = NULL;
@ -336,7 +406,7 @@ static MPMarshalledUser *mpw_marshall_read_flat(
MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1;
MPResultType defaultType = MPResultTypeDefault;
bool headerStarted = false, headerEnded = false, importRedacted = false;
for (char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
// Comment or header
if (*positionInLine == '#') {
@ -541,10 +611,39 @@ static MPMarshalledUser *mpw_marshall_read_flat(
return user;
}
static void mpw_marshall_read_json_info(
const char *in, MPMarshallInfo *info) {
// Parse JSON.
enum json_tokener_error json_error = json_tokener_success;
json_object *json_file = json_tokener_parse_verbose( in, &json_error );
if (!json_file || json_error != json_tokener_success)
return;
// Section: "export"
int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 );
if (fileFormat < 1)
return;
info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true );
info->date = mpw_mktime( mpw_get_json_string( json_file, "export.date", NULL ) );
// Section: "user"
info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
info->fullName = strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) );
info->keyID = strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) );
json_object_put( json_file );
}
static MPMarshalledUser *mpw_marshall_read_json(
char *in, const char *masterPassword, MPMarshallError *error) {
const char *in, const char *masterPassword, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) {
error->type = MPMarshallErrorStructure;
error->description = mpw_str( "No input data." );
return NULL;
}
// Parse JSON.
enum json_tokener_error json_error = json_tokener_success;
@ -683,10 +782,33 @@ static MPMarshalledUser *mpw_marshall_read_json(
return user;
}
MPMarshallInfo *mpw_marshall_read_info(
const char *in) {
MPMarshallInfo *info = malloc( sizeof( MPMarshallInfo ) );
*info = (MPMarshallInfo){ .format = MPMarshallFormatNone };
if (in && strlen( in )) {
if (in[0] == '#') {
*info = (MPMarshallInfo){ .format = MPMarshallFormatFlat };
mpw_marshall_read_flat_info( in, info );
}
else if (in[0] == '{') {
*info = (MPMarshallInfo){ .format = MPMarshallFormatJSON };
mpw_marshall_read_json_info( in, info );
}
}
return info;
}
MPMarshalledUser *mpw_marshall_read(
char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) {
const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) {
switch (inFormat) {
case MPMarshallFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return false;
case MPMarshallFormatFlat:
return mpw_marshall_read_flat( in, masterPassword, error );
case MPMarshallFormatJSON:
@ -700,6 +822,9 @@ MPMarshalledUser *mpw_marshall_read(
const MPMarshallFormat mpw_formatWithName(
const char *formatName) {
if (!formatName || !strlen( formatName ))
return MPMarshallFormatNone;
// Lower-case to standardize it.
size_t stdFormatNameSize = strlen( formatName );
char stdFormatName[stdFormatNameSize + 1];
@ -707,6 +832,8 @@ const MPMarshallFormat mpw_formatWithName(
stdFormatName[c] = (char)tolower( formatName[c] );
stdFormatName[stdFormatNameSize] = '\0';
if (strncmp( mpw_nameForFormat( MPMarshallFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatNone;
if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatFlat;
if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
@ -720,6 +847,8 @@ const char *mpw_nameForFormat(
const MPMarshallFormat format) {
switch (format) {
case MPMarshallFormatNone:
return "none";
case MPMarshallFormatFlat:
return "flat";
case MPMarshallFormatJSON:
@ -735,6 +864,8 @@ const char *mpw_marshall_format_extension(
const MPMarshallFormat format) {
switch (format) {
case MPMarshallFormatNone:
return NULL;
case MPMarshallFormatFlat:
return "mpsites";
case MPMarshallFormatJSON:

View File

@ -26,6 +26,8 @@
//// Types.
typedef enum( unsigned int, MPMarshallFormat ) {
/** Generate a key for authentication. */
MPMarshallFormatNone,
/** Generate a key for authentication. */
MPMarshallFormatFlat,
/** Generate a name for identification. */
@ -91,22 +93,42 @@ typedef struct MPMarshalledUser {
MPMarshalledSite *sites;
} MPMarshalledUser;
typedef struct MPMarshallInfo {
MPMarshallFormat format;
MPAlgorithmVersion algorithm;
const char *fullName;
const char *keyID;
bool redacted;
time_t date;
} MPMarshallInfo;
//// Marshalling.
/** Write the user and all associated data out to the given output buffer using the given marshalling format. */
bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error);
/** Try to read metadata on the sites in the input buffer. */
MPMarshallInfo *mpw_marshall_read_info(
const char *in);
/** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */
MPMarshalledUser *mpw_marshall_read(
char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error);
const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error);
//// Utilities.
/** Create a new user object ready for marshalling. */
MPMarshalledUser *mpw_marshall_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
/** Create a new site attached to the given user object, ready for marshalling. */
MPMarshalledSite *mpw_marshall_site(
MPMarshalledUser *user,
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
/** Create a new question attached to the given site object, ready for marshalling. */
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword);
/** Free the given user object and all associated data. */
bool mpw_marshal_info_free(
MPMarshallInfo *info);
bool mpw_marshal_free(
MPMarshalledUser *user);
@ -122,6 +144,9 @@ const MPMarshallFormat mpw_formatWithName(
*/
const char *mpw_nameForFormat(
const MPMarshallFormat format);
/**
* @return The file extension that's recommended for files that use the given marshalling format.
*/
const char *mpw_marshall_format_extension(
const MPMarshallFormat format);

View File

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

View File

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

View File

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

View File

@ -253,6 +253,7 @@
DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */; };
DAB7AE5D1F3D752900C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE5C1F3D752900C856B1 /* libjson-c.a */; };
DAB7AE771F3D755B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE761F3D755B00C856B1 /* libjson-c.a */; };
DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */; };
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; };
DABD39381711E29700CF925C /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366D1711E29400CF925C /* avatar-0@2x.png */; };
@ -903,6 +904,8 @@
DAB7AE731F3D755B00C856B1 /* strdup_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strdup_compat.h; sourceTree = "<group>"; };
DAB7AE741F3D755B00C856B1 /* vasprintf_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vasprintf_compat.h; sourceTree = "<group>"; };
DAB7AE761F3D755B00C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libjson-c.a"; sourceTree = "<group>"; };
DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall-util.h"; sourceTree = "<group>"; };
DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall-util.c"; sourceTree = "<group>"; };
DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
DABD360F1711E29400CF925C /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
DABD36101711E29400CF925C /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
@ -1773,6 +1776,8 @@
93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */,
93D3969393A3A46BD27D7078 /* mpw-algorithm.c */,
93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */,
DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */,
DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */,
DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */,
DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */,
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */,
@ -4030,6 +4035,7 @@
DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */,
93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */,
93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */,
DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */,
DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */,
93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */,
DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */,
@ -4327,6 +4333,8 @@
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@ -4510,6 +4518,8 @@
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@ -4598,6 +4608,8 @@
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
"$(inherited)",
);
IPHONEOS_DEPLOYMENT_TARGET = 8.0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -248,9 +248,9 @@
#endif
for (MPSiteEntity *site in user.sites) {
if (site.type & MPSiteTypeClassStored) {
if (site.type & MPResultTypeClassStateful) {
NSString *content;
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
while (!(content = [site.algorithm resolvePasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
NSString *masterPassword = nil;

View File

@ -20,14 +20,6 @@
#import "MPFixable.h"
typedef NS_ENUM( NSUInteger, MPImportResult ) {
MPImportResultSuccess,
MPImportResultCancelled,
MPImportResultInvalidPassword,
MPImportResultMalformedInput,
MPImportResultInternalError,
};
@interface MPAppDelegate_Shared(Store)
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
@ -42,10 +34,13 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type;
- (MPImportResult)importSites:(NSString *)importedSitesString
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type;
- (void)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
result:(void ( ^ )(NSError *error))resultBlock;
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock;
@end

View File

@ -18,6 +18,7 @@
#import "MPAppDelegate_Store.h"
#import "mpw-marshall.h"
#import "mpw-util.h"
#if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
@ -489,7 +490,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return;
}
MPSiteType type = activeUser.defaultType;
MPResultType type = activeUser.defaultType;
id<MPAlgorithm> algorithm = MPAlgorithmDefault;
Class entityType = [algorithm classOfType:type];
@ -506,7 +507,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
}];
}
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type {
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type {
if (site.type == type)
return site;
@ -539,328 +540,214 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return site;
}
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
- (void)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
result:(void ( ^ )(NSError *error))resultBlock {
NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
__block MPImportResult result = MPImportResultCancelled;
do {
if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword
saveInContext:context];
NSError *error = [self importSites:importData askImportPassword:importPassword askUserPassword:userPassword
saveInContext:context];
PearlMainQueue( ^{
resultBlock( error );
} );
}])
break;
usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
} while (YES);
return result;
}
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword
saveInContext:(NSManagedObjectContext *)context {
- (NSError *)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
askUserPassword:(NSString *( ^ )(NSString *userName))askUserPassword
saveInContext:(NSManagedObjectContext *)context {
// Compile patterns.
static NSRegularExpression *headerPattern;
static NSArray *sitePatterns;
NSError *error = nil;
if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc]
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:(NSRegularExpressionOptions)0 error:&error];
if (error) {
MPError( error, @"Error loading the header pattern." );
return MPImportResultInternalError;
// Read metadata for the import file.
MPMarshallInfo *info = mpw_marshall_read_info( importData.UTF8String );
if (info->format == MPMarshallFormatNone)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
@"type" : @(MPMarshallErrorFormat),
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
}]), @"While importing sites." );
// Get master password for import file.
MPKey *importKey;
NSString *importMasterPassword;
do {
importMasterPassword = askImportPassword( @(info->fullName) );
if (!importMasterPassword) {
inf( @"Import cancelled." );
mpw_marshal_info_free( info );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
}
}
if (!sitePatterns) {
sitePatterns = @[
[[NSRegularExpression alloc] // Format 0
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error],
[[NSRegularExpression alloc] // Format 1
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error]
];
if (error) {
MPError( error, @"Error loading the site patterns." );
return MPImportResultInternalError;
}
}
importKey = [[MPKey alloc] initForFullName:@(info->fullName) withMasterPassword:importMasterPassword];
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( info->algorithm )] encodeHex]
caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame);
// Parse import data.
inf( @"Importing sites." );
NSUInteger importFormat = 0;
__block MPUserEntity *user = nil;
NSUInteger importAvatar = NSNotFound;
NSData *importKeyID = nil;
NSString *importBundleVersion = nil, *importUserName = nil;
id<MPAlgorithm> importAlgorithm = nil;
MPSiteType importDefaultType = (MPSiteType)0;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *sitesToDelete = [NSMutableSet set];
NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header
if (!headerStarted) {
if ([importedSiteLine isEqualToString:@"##"])
headerStarted = YES;
continue;
}
if (headerEnded)
continue;
if ([importedSiteLine isEqualToString:@"##"]) {
headerEnded = YES;
continue;
MPMarshallError importError = { .type = MPMarshallSuccess };
MPMarshalledUser *importUser = mpw_marshall_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError );
mpw_marshal_info_free( info );
@try {
if (!importUser || importError.type != MPMarshallSuccess)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
@"type" : @(importError.type),
NSLocalizedDescriptionKey: @(importError.description),
}]), @"While importing sites." );
// Find an existing user to update.
NSError *error = nil;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", @(importUser->fullName)];
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users)
return MPError( error, @"While looking for user: %@.", @(importUser->fullName) );
if ([users count] > 1)
return MPMakeError( @"While looking for user: %@, found more than one: %zu",
@(importUser->fullName), (size_t)[users count] );
// Get master key for user.
MPUserEntity *user = [users lastObject];
MPKey *userKey = importKey;
while (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) {
NSString *userMasterPassword = askUserPassword( user.name );
if (!userMasterPassword) {
inf( @"Import cancelled." );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
}
// Header
if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid header format in line: %@", importedSiteLine );
return MPImportResultMalformedInput;
}
NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
if ([headerName isEqualToString:@"Format"]) {
importFormat = (NSUInteger)[headerValue integerValue];
if (importFormat >= [sitePatterns count]) {
err( @"Unsupported import format: %lu", (unsigned long)importFormat );
return MPImportResultInternalError;
}
}
if (([headerName isEqualToString:@"User Name"] || [headerName isEqualToString:@"Full Name"]) && !importUserName) {
importUserName = headerValue;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users) {
MPError( error, @"While looking for user: %@.", importUserName );
return MPImportResultInternalError;
}
if ([users count] > 1) {
err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] );
return MPImportResultInternalError;
}
user = [users lastObject];
dbg( @"Existing user? %@", [user debugDescription] );
}
if ([headerName isEqualToString:@"Avatar"])
importAvatar = (NSUInteger)[headerValue integerValue];
if ([headerName isEqualToString:@"Key ID"])
importKeyID = [headerValue decodeHex];
if ([headerName isEqualToString:@"Version"]) {
importBundleVersion = headerValue;
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
}
if ([headerName isEqualToString:@"Algorithm"])
importAlgorithm = MPAlgorithmForVersion( (MPAlgorithmVersion)[headerValue integerValue] );
if ([headerName isEqualToString:@"Default Type"])
importDefaultType = (MPSiteType)[headerValue integerValue];
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
}
continue;
}
if (!headerEnded)
continue;
if (![importUserName length])
return MPImportResultMalformedInput;
if (![importedSiteLine length])
continue;
// Site
NSRegularExpression *sitePattern = sitePatterns[importFormat];
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid site format in line: %@", importedSiteLine );
return MPImportResultMalformedInput;
}
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent;
switch (importFormat) {
case 0:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = @"";
loginName = @"";
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
break;
case 1:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
if ([counter length])
counter = [counter substringFromIndex:1]; // Strip the leading colon.
loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]];
break;
default:
err( @"Unexpected import format: %lu", (unsigned long)importFormat );
return MPImportResultInternalError;
userKey = [[MPKey alloc] initForFullName:@(importUser->fullName) withMasterPassword:userMasterPassword];
}
// Find existing site.
if (user) {
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
if (!existingSites) {
MPError( error, @"Lookup of existing sites failed for site: %@, user: %@.", siteName, user.userID );
return MPImportResultInternalError;
}
if ([existingSites count]) {
dbg( @"Existing sites: %@", existingSites );
[sitesToDelete addObjectsFromArray:existingSites];
}
// Update or create user.
if (!user) {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = @(importUser->fullName);
}
[importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
}
// Ask for confirmation to import these sites and the master password of the user.
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count],
(unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] );
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count],
[sitesToDelete count] );
if (!userMasterPassword) {
inf( @"Import cancelled." );
return MPImportResultCancelled;
}
MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword];
if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID])
return MPImportResultInvalidPassword;
__block MPKey *importKey = userKey;
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )];
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
return MPImportResultInvalidPassword;
// Delete existing sites.
if (sitesToDelete.count)
[sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] );
[context deleteObject:obj];
}];
// Make sure there is a user.
if (user) {
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
if (importDefaultType)
user.defaultType = importDefaultType;
dbg( @"Updating User: %@", [user debugDescription] );
}
else {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = importUserName;
user.algorithm = MPAlgorithmDefault;
user.algorithm = MPAlgorithmForVersion( importUser->algorithm );
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
user.defaultType = importDefaultType?: user.algorithm.defaultType;
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created User: %@", [user debugDescription] );
}
user.avatar = importUser->avatar;
user.defaultType = importUser->defaultType;
user.lastUsed = [NSDate dateWithTimeIntervalSince1970:MAX( user.lastUsed.timeIntervalSince1970, importUser->lastUsed )];
dbg( @"Importing user: %@", [user debugDescription] );
// Import new sites.
for (NSArray *siteElements in importedSiteSites) {
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
MPSiteType type = (MPSiteType)[siteElements[2] integerValue];
MPAlgorithmVersion version = (MPAlgorithmVersion)[siteElements[3] integerValue];
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
NSString *siteName = siteElements[6];
NSString *exportContent = siteElements[7];
// Update or create sites.
for (size_t s = 0; s < importUser->sites_count; ++s) {
MPMarshalledSite *importSite = &importUser->sites[s];
// Create new site.
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version );
Class entityType = [algorithm classOfType:type];
if (!entityType) {
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
return MPImportResultInternalError;
// Find an existing site to update.
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", @(importSite->name), user];
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
if (!existingSites)
return MPError( error, @"Lookup of existing sites failed for site: %@, user: %@", @(importSite->name), user.userID );
if ([existingSites count])
// Update existing site.
for (MPSiteEntity *site in existingSites) {
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
dbg( @"Updated site: %@", [site debugDescription] );
}
else {
// Create new site.
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( importSite->algorithm );
Class entityType = [algorithm classOfType:importSite->type];
if (!entityType)
return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->name), (long)importSite->type );
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.user = user;
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
dbg( @"Created site: %@", [site debugDescription] );
}
}
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.name = siteName;
site.loginName = loginName;
site.user = user;
site.type = type;
site.uses = uses;
site.lastUsed = lastUsed;
site.algorithm = algorithm;
if ([exportContent length]) {
if (clearText)
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
else
[site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey];
}
if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound)
((MPGeneratedSiteEntity *)site).counter = counter;
dbg( @"Created Site: %@", [site debugDescription] );
if (![context saveToStore])
return MPMakeError( @"Failed saving imported changes." );
inf( @"Import completed successfully." );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey: user
}];
return nil;
}
@finally {
mpw_marshal_free( importUser );
}
if (![context saveToStore])
return MPImportResultInternalError;
inf( @"Import completed successfully." );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey: user
}];
return MPImportResultSuccess;
}
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
- (void)importSite:(const MPMarshalledSite *)importSite protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site
usingKey:(MPKey *)userKey {
MPUserEntity *activeUser = [self activeUserForMainThread];
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
site.name = @(importSite->name);
if (importSite->content)
[site.algorithm importPassword:@(importSite->content) protectedByKey:importKey intoSite:site usingKey:userKey];
site.type = importSite->type;
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
((MPGeneratedSiteEntity *)site).counter = importSite->counter;
site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
site.loginName = importSite->loginName? @(importSite->loginName): nil;
site.loginGenerated = importSite->loginGenerated;
site.url = importSite->url? @(importSite->url): nil;
site.uses = importSite->uses;
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
}
MPMarshalledUser exportUser = mpw_marshall_user( activeUser.name.UTF8String,
[self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version );
exportUser.avatar = activeUser.avatar;
exportUser.defaultType = activeUser.defaultType;
exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970;
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock {
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user = [self activeUserInContext:context];
NSString *masterPassword = askImportPassword( user.name );
for (MPSiteEntity *site in activeUser.sites) {
MPMarshalledSite exportSite = mpw_marshall_site( &exportUser,
site.name.UTF8String, site.type, site.counter, site.algorithm.version );
exportSite.loginName = site.loginName.UTF8String;
exportSite.url = site.url.UTF8String;
exportSite.uses = site.uses;
exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
MPMarshalledUser *exportUser = mpw_marshall_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version );
exportUser->redacted = !revealPasswords;
exportUser->avatar = (unsigned int)user.avatar;
exportUser->defaultType = user.defaultType;
exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970;
for (MPSiteQuestionEntity *siteQuestion in site.questions)
mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String );
}
for (MPSiteEntity *site in user.sites) {
MPCounterValue counter = MPCounterValueInitial;
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)site).counter;
NSString *content = revealPasswords
? [site.algorithm exportPasswordForSite:site usingKey:self.key]
: [site.algorithm resolvePasswordForSite:site usingKey:self.key];
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser );
MPMarshalledSite *exportSite = mpw_marshall_site( exportUser,
site.name.UTF8String, site.type, counter, site.algorithm.version );
exportSite->content = content.UTF8String;
exportSite->loginName = site.loginName.UTF8String;
exportSite->loginGenerated = site.loginGenerated;
exportSite->url = site.url.UTF8String;
exportSite->uses = (unsigned int)site.uses;
exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
for (MPSiteQuestionEntity *siteQuestion in site.questions)
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
}
char *export = NULL;
MPMarshallError exportError = (MPMarshallError){ .type= MPMarshallSuccess };
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser, &exportError );
NSString *mpsites = nil;
if (export && exportError.type == MPMarshallSuccess)
mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
mpw_free_string( export );
resultBlock( mpsites, exportError.type == MPMarshallSuccess? nil:
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
@"type" : @(exportError.type),
NSLocalizedDescriptionKey: @(exportError.description),
}] );
}];
}
@end

View File

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

View File

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

View File

@ -22,6 +22,7 @@
__BEGIN_DECLS
extern NSString *const MPErrorDomain;
extern NSInteger const MPErrorHangCode;
extern NSInteger const MPErrorMarshallCode;
extern NSString *const MPSignedInNotification;
extern NSString *const MPSignedOutNotification;
@ -38,13 +39,20 @@ __END_DECLS
#ifdef CRASHLYTICS
#define MPError(error_, message, ...) ({ \
err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
NSError *error = error_; \
err( message @"%@%@", ##__VA_ARGS__, error && [message length]? @"\n": @"", [error fullDescription]?: @"" ); \
\
if ([[MPConfig get].sendInfo boolValue]) { \
[[Crashlytics sharedInstance] recordError:error_ withAdditionalUserInfo:@{ \
[[Crashlytics sharedInstance] recordError:error withAdditionalUserInfo:@{ \
@"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \
}]; \
} \
error; \
})
#define MPMakeError(message, ...) ({ \
MPError( [NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ \
NSLocalizedDescriptionKey: strf( message, ##__VA_ARGS__ ) \
}], @"" ); \
})
#else
#define MPError(error_, message, ...) ({ \

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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