Move site to root in this branch.
This commit is contained in:
parent
409f005eec
commit
d0cd8ae288
207
README.md
207
README.md
@ -1,207 +0,0 @@
|
||||
[![Travis CI](http://img.shields.io/travis-ci/Lyndir/MasterPassword.png)](https://travis-ci.org/Lyndir/MasterPassword)
|
||||
[![Join the chat at https://gitter.im/lyndir/MasterPassword](https://badges.gitter.im/lyndir/MasterPassword.svg)](https://gitter.im/lyndir/MasterPassword?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[![license](https://img.shields.io/github/license/lyndir/masterpassword.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
|
||||
|
||||
|
||||
# [Master Password •••|](http://masterpasswordapp.com)
|
||||
|
||||
Master Password is a completely new way of thinking about passwords.
|
||||
|
||||
It consists of an algorithm that implements the core idea and applications for various platforms making the alogirthm available to users on a variety of devices and platforms.
|
||||
|
||||
To skip the intro and go straight to the information on how to use the code, [click here](#source-code).
|
||||
|
||||
Master Password is available for [📲 iOS](https://itunes.apple.com/app/id510296984), [🖥 macOS](https://ssl.masterpasswordapp.com/masterpassword-mac.zip), [📲 Android](https://ssl.masterpasswordapp.com/masterpassword-android.apk), [🖥 Desktop](https://ssl.masterpasswordapp.com/masterpassword-gui.jar), and [⌨ Console](https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz).
|
||||
|
||||
Master Password is also available from the following package managers: [macOS: Homebrew](https://brew.sh/) (`brew install mpw`).
|
||||
Get in touch if you are interested in adding Master Password to any other package managers.
|
||||
|
||||
There are many reasons for using Master Password instead of an ordinary password manager, read below for the details, but if you want my personal favourites, they would be:
|
||||
|
||||
- I don't need to worry about keeping backups of my countless authentication credentials.
|
||||
- I don't need to worry that when I travel, I might not have access to my passwords vault.
|
||||
- I don't need to trust an external party, proprietary code or a service to be online and stay online.
|
||||
- If I feel at risk of my device being stolen or confiscated, I can set a fake master password, delete my user or wipe it worry-free.
|
||||
|
||||
We also have a [Frequently Asked Questions](#faq).
|
||||
|
||||
|
||||
|
||||
## What is a password?
|
||||
|
||||
Ah, the "password". Somehow, passwords have become the default solution to authentication across the web. We've long since accepted this as the way things are, but let's stop to think for a moment about what passwords actually are:
|
||||
|
||||
A password is a secret that is known only to the party providing a service and the party that should be allowed access to this service.
|
||||
|
||||
Simple enough - a secret that you know and your website knows but nobody else, thereby guaranteeing that you and only you have access to your account on this website. Unfortunately, in practice, the ubiquitous use of passwords has us completely overwhelmed. And the only way we can cope with that is by finding ways of making the problem manageable.
|
||||
|
||||
|
||||
|
||||
## What's the problem?
|
||||
|
||||
Coming up with a secret password is pretty easy. Say you're organizing a secret meeting and will only let people in if they know the password at the door. You tell those you trust, the password for tonight's meeting is "purple oranges with a smile".
|
||||
|
||||
The problem we have in our daily lives, however, is the fact that we need secret passwords for almost everything now. A password for our email, twitter, 9gag, facebook, imgur, amazon, ebay, paypal, bank, reddit, etc. And every time we want to use a new site, we need another one. The problem now becomes clear: passwords are meant to be remembered and recalled with ease when needed, but this becomes impossible when we have secrets for every distinct activity in our lives.
|
||||
|
||||
We cannot recall passwords the way we are expected to when there are too many.
|
||||
|
||||
|
||||
|
||||
## Coping
|
||||
|
||||
Life gives us no advice on how to deal with this problem. So we find our own ways:
|
||||
|
||||
- We use a single personal secret for all our websites, thereby violating the secrecy of these passwords (eg. you've given your email secret to twitter).
|
||||
- We use simple variations of a personal secret or pattern, thereby trivializing the complexity of these passwords (eg. google98, twitter98; reversals, eg. 8991elgoog)
|
||||
- We use external means of remembering passwords, thereby increasing the risk of loss (both loss of access when we lose the tool and theft when a thief finds our tool)
|
||||
|
||||
These coping mechanisms come in various forms, and they all have down-sides, because at the root of each of these lies an undeniable truth:
|
||||
|
||||
Our passwords are no longer true to the original definition.
|
||||
|
||||
|
||||
|
||||
## Master Password's approach
|
||||
|
||||
The theory behind Master Password starts with accepting that it is impossible to keep track of passwords for all your accounts. Instead, we return to the core premise of the password: a secret phrase that you can remember easily, all by yourself.
|
||||
|
||||
Master Password solves this problem by letting you remember one and only one password. You use this password with Master Password only. Master Password then gives you access to any website or service you want by creating a website-specific key for it.
|
||||
|
||||
1. You sign into Master Password using your one password.
|
||||
2. You ask Master Password for the key to enter your website, eg. twitter.
|
||||
3. You log into twitter using your username and the key from Master Password.
|
||||
|
||||
Master Password is *not* a password manager. It does not store your website passwords. Therefore, there is zero risk of you losing your website passwords (or them falling in the wrong hands). Master Password simply uses your one password and the name of the site to generate a site-specific secret.
|
||||
|
||||
|
||||
|
||||
## Benefits
|
||||
|
||||
- You don't need to think up a new strong password every time you make a new account - Master Password gives you the key for it.
|
||||
- You don't need to try remembering a password you created two years ago for that one account - Master Password just gives you the key for it.
|
||||
- You don't need to worry about getting into that account you made at work after you come home because you don't have your office passwords with you - Master Password is availale everywhere, even offline.
|
||||
- You don't need to try to keep password lists in sync or stored somewhere easily accessible - Master Password keys can be created anywhere.
|
||||
- You don't need to worry what you'll do if your computer dies or you need to log into your bank while you're in the airport transit zone - your Master Password keys are always available, even when starting empty.
|
||||
- You don't need to worry about your password manager website getting hacked, your phone getting duplicated, somebody taking a picture of your passwords book - Master Password stores no secrets.
|
||||
|
||||
|
||||
|
||||
## How does it work?
|
||||
|
||||
The details of how Master Password works [are available here](http://masterpasswordapp.com/algorithm.html).
|
||||
|
||||
In short:
|
||||
|
||||
master-key = SCRYPT( user-name, master-password )
|
||||
site-key = HMAC-SHA-256( site-name . site-counter, master-key )
|
||||
site-password = PW-TEMPLATE( site-key, site-template )
|
||||
|
||||
Master Password can derive the `site-password` in an entirely stateless manner. It is therefore better defined as a calculator than a manager. It is the user's responsibility to remember the inputs: `user-name`, `master-password`, `site-name`, `site-counter` and `site-template`.
|
||||
|
||||
We standardize `user-name` as your full name, `site-name` as the domain name of the site, `site-counter` to `1` (unless you explicitly increment it) and `site-template` to `Long Password`; as a result the only token the user really needs to remember actively is `master-password`.
|
||||
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
1. If I lose my master password and need to set a new one, will I need to change all of my site passwords?
|
||||
|
||||
Yes. If your master password is compromised, it is only sensible for you to change all of your site passwords. Just like if you lose the keys in your pocket, you'll have to change all the locks they open. Master Password effectively enforces this security practice.
|
||||
|
||||
2. But what if I just forget my master password or I just want to change it to something else?
|
||||
|
||||
Sorry, still yes. Your master password is the secret component to your Master Password identity. If it changes, your identity changes. I wholly encourage you to think very carefully about what makes for a really memorable and good master password before just diving in with something lazy. A short phrase works great, eg. `banana coloured duckling`.
|
||||
|
||||
3. Doesn't this mean an attacker can reverse my master password from any of my site passwords?
|
||||
|
||||
Technically, yes. Practically, no.
|
||||
|
||||
You could argue that site passwords are "breadcrumbs" of your master password, but the same argument would suggest encrypted messages are breadcrumbs to the encryption key. Encryption works because it is computationally unfeasible to "guess" the encryption key that made the encrypted message, just like Master Password works because it is computationally unfeasible to "guess" your master password that made the site password.
|
||||
|
||||
4. The second step is just a HMAC-SHA-256, doesn't that make the SCRYPT completely pointless?
|
||||
|
||||
No. They are used for different reasons and one is not weaker than the other.
|
||||
|
||||
HMAC-SHA-256 is much faster to compute than SCRYPT, which leads some people to think "all an attacker needs to do is brute-force the SHA and ignore the SCRYPT". The reality is that the HMAC-SHA-256 guards a 64-byte authentication key (the `master-key`) which makes the search space for brute-forcing the HMAC wildly too large to compute.
|
||||
The `master-password` on the other hand, is only a simple phrase, which means its search space is much smaller. This is why it is guarded by a much tougher SCRYPT operation.
|
||||
|
||||
5. I have another question.
|
||||
|
||||
Please don't hesitate to [get in touch](#support), we're more than happy to answer all your Master Password questions. Any problems or suggestions can be reported [as GitHub issues](https://github.com/Lyndir/MasterPassword/issues).
|
||||
|
||||
|
||||
|
||||
|
||||
# Source Code
|
||||
|
||||
Master Password's algorithm is [documented](http://masterpasswordapp.com/algorithm.html) and its implementation is Free Software (GPLv3).
|
||||
|
||||
|
||||
|
||||
## Components
|
||||
|
||||
There are several components available here. As an end-user, you can currently use the iOS app, the Android app, the OS X app, the Java desktop app, the C CLI app or the Java CLI app. There are also several components that are useful for developers:
|
||||
|
||||
- `core/c`: This is the reference implementation of the Master Password algorithm, written in C.
|
||||
- `core/java/algorithm`: This is a Java implementation of the Master Password algorithm.
|
||||
- `core/java/model`: This is an object model to simplify use of Master Password by Java applications.
|
||||
- `core/java/tests`: These are Java integration tests designed to ensure Master Password performs as expected.
|
||||
- `platform-android`: This is the official Android implementation of Master Password in Java.
|
||||
- `platform-darwin`: This is the official iOS and OS X implementation of Master Password in Objective-C.
|
||||
- `platform-independent/cli-c`: This is the platform-independent console implementation of Master Password, written in C.
|
||||
- `platform-independent/cli-java`: This is the platform-independent console implementation of Master Password, written in Java.
|
||||
- `platform-independent/gui-java`: This is the platform-independent desktop implementation of Master Password, written in Java.
|
||||
- `platform-independent/web-js`: This is the platform-independent browser application for Master Password, written in JavaScript.
|
||||
|
||||
|
||||
|
||||
## Building and running
|
||||
|
||||
|
||||
### macOS or iOS
|
||||
|
||||
Make sure you have all relevant submodules checked out.
|
||||
|
||||
Go into `platform-darwin` and open `MasterPassword.xcworkspace` in Xcode. Select the desired target from the Scheme Selector and build, run or archive.
|
||||
|
||||
|
||||
### Web
|
||||
|
||||
Make sure you have all relevant submodules checked out.
|
||||
|
||||
Go into `platform-independent/web-js` and open `index.html` in your browser. You should be able to run this locally, there is no need for hosting or an application server.
|
||||
|
||||
|
||||
### Java
|
||||
|
||||
Go into the `gradle` directory and run `./gradlew build`. All Java components will then be built:
|
||||
|
||||
- `platform-independent/gui-java/build/distributions`:
|
||||
contains an archive with the Master Password Java GUI. Unpack it and run the `gui` script.
|
||||
- `platform-independent/cli-java/build/distributions`:
|
||||
contains an archive with the Master Password Java command-line interface. Unpack it and run the `cli` script.
|
||||
- `platform-android/build/outputs/apk`:
|
||||
contains the Android application package. Install it on your Android device.
|
||||
|
||||
Note that in order to build the Android application, you will need to have the Android SDK installed and either have the environment variable `ANDROID_HOME` set to its location or a `gradle/local.properties` file with its location, eg. (for Homebrew users who installed the SDK using `brew install android-sdk`):
|
||||
|
||||
sdk.dir=/usr/local/opt/android-sdk
|
||||
|
||||
|
||||
### Native CLI
|
||||
|
||||
Go into the `platform-independent/cli-c` directory and run `./build`. The native command-line client will then be built.
|
||||
|
||||
For detailed instructions, see [the native CLI instructions](platform-independent/cli-c/README.md).
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
Feel free to contribute by forking the project, reporting issues or joining the discussion on:
|
||||
|
||||
- [Gitter](https://gitter.im/lyndir/MasterPassword)
|
||||
- #masterpassword (on chat.freenode.net)
|
||||
- #masterpassword:lyndir.com (on Matrix)
|
||||
- masterpassword@lyndir.com
|
@ -1,12 +1,15 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
|
||||
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
|
||||
<meta name="apple-itunes-app" content="app-id=510296984" />
|
||||
|
||||
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}">
|
||||
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}">
|
||||
<link rel="shortcut icon" href="img/favicon.png" type="image/x-png" />
|
||||
<link rel="icon" href="img/favicon.png" type="image/x-png" />
|
||||
</head>
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B |
Before Width: | Height: | Size: 787 B After Width: | Height: | Size: 787 B |
606
core/c/aes.c
606
core/c/aes.c
@ -1,606 +0,0 @@
|
||||
/*
|
||||
|
||||
Source: https://github.com/kokke/tiny-AES-c
|
||||
|
||||
This is an implementation of the AES algorithm, specifically ECB and CBC mode.
|
||||
Block size can be chosen in aes.h - available choices are AES128, AES192, AES256.
|
||||
|
||||
The implementation is verified against the test vectors in:
|
||||
National Institute of Standards and Technology Special Publication 800-38A 2001 ED
|
||||
|
||||
ECB-AES128
|
||||
----------
|
||||
|
||||
plain-text:
|
||||
6bc1bee22e409f96e93d7e117393172a
|
||||
ae2d8a571e03ac9c9eb76fac45af8e51
|
||||
30c81c46a35ce411e5fbc1191a0a52ef
|
||||
f69f2445df4f9b17ad2b417be66c3710
|
||||
|
||||
key:
|
||||
2b7e151628aed2a6abf7158809cf4f3c
|
||||
|
||||
resulting cipher
|
||||
3ad77bb40d7a3660a89ecaf32466ef97
|
||||
f5d3d58503b9699de785895a96fdbaaf
|
||||
43b1cd7f598ece23881b00e3ed030688
|
||||
7b0c785e27e8ad3f8223207104725dd4
|
||||
|
||||
|
||||
NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
|
||||
You should pad the end of the string with zeros if this is not the case.
|
||||
For AES192/256 the block size is proportionally larger.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Includes: */
|
||||
/*****************************************************************************/
|
||||
#include <string.h>
|
||||
#include "aes.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Defines: */
|
||||
/*****************************************************************************/
|
||||
// The number of columns comprising a state in AES. This is a constant in AES. Value=4
|
||||
#define Nb 4
|
||||
|
||||
#if defined(AES_256) && (AES_256 == 1)
|
||||
#define Nk 8
|
||||
#define KEYLEN 32
|
||||
#define Nr 14
|
||||
#define keyExpSize 240
|
||||
#elif defined(AES_192) && (AES_192 == 1)
|
||||
#define Nk 6
|
||||
#define KEYLEN 24
|
||||
#define Nr 12
|
||||
#define keyExpSize 208
|
||||
#elif defined(AES_128) && (AES_128 == 1)
|
||||
#define Nk 4 // The number of 32 bit words in a key.
|
||||
#define KEYLEN 16 // Key length in bytes
|
||||
#define Nr 10 // The number of rounds in AES Cipher.
|
||||
#define keyExpSize 176
|
||||
#else
|
||||
#error Must define either AES_128, AES_192 or AES_256.
|
||||
#endif
|
||||
|
||||
// jcallan@github points out that declaring Multiply as a function
|
||||
// reduces code size considerably with the Keil ARM compiler.
|
||||
// See this link for more information: https://github.com/kokke/tiny-AES128-C/pull/3
|
||||
#ifndef MULTIPLY_AS_A_FUNCTION
|
||||
#define MULTIPLY_AS_A_FUNCTION 0
|
||||
#endif
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Private variables: */
|
||||
/*****************************************************************************/
|
||||
// state - array holding the intermediate results during decryption.
|
||||
typedef uint8_t state_t[4][4];
|
||||
static state_t* state;
|
||||
|
||||
// The array that stores the round keys.
|
||||
static uint8_t RoundKey[keyExpSize];
|
||||
|
||||
// The Key input to the AES Program
|
||||
static const uint8_t* Key;
|
||||
|
||||
#if defined(AES_CBC) && AES_CBC
|
||||
// Initial Vector used only for CBC mode
|
||||
static uint8_t* Iv;
|
||||
#endif
|
||||
|
||||
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
|
||||
// The numbers below can be computed dynamically trading ROM for RAM -
|
||||
// This can be useful in (embedded) bootloader applications, where ROM is often limited.
|
||||
static const uint8_t sbox[256] = {
|
||||
//0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
|
||||
|
||||
static const uint8_t rsbox[256] = {
|
||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
||||
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
||||
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
||||
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
||||
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
||||
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
||||
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
||||
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
||||
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
||||
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
||||
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
||||
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
||||
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
||||
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
||||
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
|
||||
|
||||
// The round constant word array, Rcon[i], contains the values given by
|
||||
// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
|
||||
static const uint8_t Rcon[11] = {
|
||||
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
|
||||
|
||||
/*
|
||||
* Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES128-C/pull/12),
|
||||
* that you can remove most of the elements in the Rcon array, because they are unused.
|
||||
*
|
||||
* From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon
|
||||
*
|
||||
* "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed),
|
||||
* up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm."
|
||||
*
|
||||
* ... which is why the full array below has been 'disabled' below.
|
||||
*/
|
||||
#if 0
|
||||
static const uint8_t Rcon[256] = {
|
||||
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
||||
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
||||
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
||||
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
||||
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
||||
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
||||
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
||||
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
||||
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
||||
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
||||
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
||||
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
||||
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
||||
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d };
|
||||
#endif
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Private functions: */
|
||||
/*****************************************************************************/
|
||||
static uint8_t getSBoxValue(uint8_t num)
|
||||
{
|
||||
return sbox[num];
|
||||
}
|
||||
|
||||
static uint8_t getSBoxInvert(uint8_t num)
|
||||
{
|
||||
return rsbox[num];
|
||||
}
|
||||
|
||||
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
|
||||
static void KeyExpansion(void)
|
||||
{
|
||||
uint32_t i;
|
||||
uint8_t k, tempa[4]; // Used for the column/row operations
|
||||
|
||||
// The first round key is the key itself.
|
||||
for (i = 0; i < Nk; ++i)
|
||||
{
|
||||
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
|
||||
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
|
||||
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
|
||||
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
|
||||
}
|
||||
|
||||
// All other round keys are found from the previous round keys.
|
||||
//i == Nk
|
||||
for (; i < Nb * (Nr + 1); ++i)
|
||||
{
|
||||
{
|
||||
tempa[0]=RoundKey[(i-1) * 4 + 0];
|
||||
tempa[1]=RoundKey[(i-1) * 4 + 1];
|
||||
tempa[2]=RoundKey[(i-1) * 4 + 2];
|
||||
tempa[3]=RoundKey[(i-1) * 4 + 3];
|
||||
}
|
||||
|
||||
if (i % Nk == 0)
|
||||
{
|
||||
// This function shifts the 4 bytes in a word to the left once.
|
||||
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
|
||||
|
||||
// Function RotWord()
|
||||
{
|
||||
k = tempa[0];
|
||||
tempa[0] = tempa[1];
|
||||
tempa[1] = tempa[2];
|
||||
tempa[2] = tempa[3];
|
||||
tempa[3] = k;
|
||||
}
|
||||
|
||||
// SubWord() is a function that takes a four-byte input word and
|
||||
// applies the S-box to each of the four bytes to produce an output word.
|
||||
|
||||
// Function Subword()
|
||||
{
|
||||
tempa[0] = getSBoxValue(tempa[0]);
|
||||
tempa[1] = getSBoxValue(tempa[1]);
|
||||
tempa[2] = getSBoxValue(tempa[2]);
|
||||
tempa[3] = getSBoxValue(tempa[3]);
|
||||
}
|
||||
|
||||
tempa[0] = tempa[0] ^ Rcon[i/Nk];
|
||||
}
|
||||
#if defined(AES256) && (AES256 == 1)
|
||||
if (i % Nk == 4)
|
||||
{
|
||||
// Function Subword()
|
||||
{
|
||||
tempa[0] = getSBoxValue(tempa[0]);
|
||||
tempa[1] = getSBoxValue(tempa[1]);
|
||||
tempa[2] = getSBoxValue(tempa[2]);
|
||||
tempa[3] = getSBoxValue(tempa[3]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
RoundKey[i * 4 + 0] = RoundKey[(i - Nk) * 4 + 0] ^ tempa[0];
|
||||
RoundKey[i * 4 + 1] = RoundKey[(i - Nk) * 4 + 1] ^ tempa[1];
|
||||
RoundKey[i * 4 + 2] = RoundKey[(i - Nk) * 4 + 2] ^ tempa[2];
|
||||
RoundKey[i * 4 + 3] = RoundKey[(i - Nk) * 4 + 3] ^ tempa[3];
|
||||
}
|
||||
}
|
||||
|
||||
// This function adds the round key to state.
|
||||
// The round key is added to the state by an XOR function.
|
||||
static void AddRoundKey(uint8_t round)
|
||||
{
|
||||
uint8_t i,j;
|
||||
for (i=0;i<4;++i)
|
||||
{
|
||||
for (j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[i][j] ^= RoundKey[round * Nb * 4 + i * Nb + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The SubBytes Function Substitutes the values in the
|
||||
// state matrix with values in an S-box.
|
||||
static void SubBytes(void)
|
||||
{
|
||||
uint8_t i, j;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
for (j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[j][i] = getSBoxValue((*state)[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The ShiftRows() function shifts the rows in the state to the left.
|
||||
// Each row is shifted with different offset.
|
||||
// Offset = Row number. So the first row is not shifted.
|
||||
static void ShiftRows(void)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// Rotate first row 1 columns to left
|
||||
temp = (*state)[0][1];
|
||||
(*state)[0][1] = (*state)[1][1];
|
||||
(*state)[1][1] = (*state)[2][1];
|
||||
(*state)[2][1] = (*state)[3][1];
|
||||
(*state)[3][1] = temp;
|
||||
|
||||
// Rotate second row 2 columns to left
|
||||
temp = (*state)[0][2];
|
||||
(*state)[0][2] = (*state)[2][2];
|
||||
(*state)[2][2] = temp;
|
||||
|
||||
temp = (*state)[1][2];
|
||||
(*state)[1][2] = (*state)[3][2];
|
||||
(*state)[3][2] = temp;
|
||||
|
||||
// Rotate third row 3 columns to left
|
||||
temp = (*state)[0][3];
|
||||
(*state)[0][3] = (*state)[3][3];
|
||||
(*state)[3][3] = (*state)[2][3];
|
||||
(*state)[2][3] = (*state)[1][3];
|
||||
(*state)[1][3] = temp;
|
||||
}
|
||||
|
||||
static uint8_t xtime(uint8_t x)
|
||||
{
|
||||
return (uint8_t)((x << 1) ^ (((x >> 7) & 1) * 0x1b));
|
||||
}
|
||||
|
||||
// MixColumns function mixes the columns of the state matrix
|
||||
static void MixColumns(void)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t Tmp,Tm,t;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
t = (*state)[i][0];
|
||||
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
|
||||
Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply is used to multiply numbers in the field GF(2^8)
|
||||
#if MULTIPLY_AS_A_FUNCTION
|
||||
static uint8_t Multiply(uint8_t x, uint8_t y)
|
||||
{
|
||||
return (((y & 1) * x) ^
|
||||
((y>>1 & 1) * xtime(x)) ^
|
||||
((y>>2 & 1) * xtime(xtime(x))) ^
|
||||
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^
|
||||
((y>>4 & 1) * xtime(xtime(xtime(xtime(x))))));
|
||||
}
|
||||
#else
|
||||
#define Multiply(x, y) (uint8_t) \
|
||||
( ((y & 1) * x) ^ \
|
||||
((y>>1 & 1) * xtime(x)) ^ \
|
||||
((y>>2 & 1) * xtime(xtime(x))) ^ \
|
||||
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \
|
||||
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \
|
||||
|
||||
#endif
|
||||
|
||||
// MixColumns function mixes the columns of the state matrix.
|
||||
// The method used to multiply may be difficult to understand for the inexperienced.
|
||||
// Please use the references to gain more information.
|
||||
static void InvMixColumns(void)
|
||||
{
|
||||
int i;
|
||||
uint8_t a, b, c, d;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
a = (*state)[i][0];
|
||||
b = (*state)[i][1];
|
||||
c = (*state)[i][2];
|
||||
d = (*state)[i][3];
|
||||
|
||||
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
|
||||
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
|
||||
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
|
||||
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The SubBytes Function Substitutes the values in the
|
||||
// state matrix with values in an S-box.
|
||||
static void InvSubBytes(void)
|
||||
{
|
||||
uint8_t i,j;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
for (j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void InvShiftRows(void)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// Rotate first row 1 columns to right
|
||||
temp = (*state)[3][1];
|
||||
(*state)[3][1] = (*state)[2][1];
|
||||
(*state)[2][1] = (*state)[1][1];
|
||||
(*state)[1][1] = (*state)[0][1];
|
||||
(*state)[0][1] = temp;
|
||||
|
||||
// Rotate second row 2 columns to right
|
||||
temp = (*state)[0][2];
|
||||
(*state)[0][2] = (*state)[2][2];
|
||||
(*state)[2][2] = temp;
|
||||
|
||||
temp = (*state)[1][2];
|
||||
(*state)[1][2] = (*state)[3][2];
|
||||
(*state)[3][2] = temp;
|
||||
|
||||
// Rotate third row 3 columns to right
|
||||
temp = (*state)[0][3];
|
||||
(*state)[0][3] = (*state)[1][3];
|
||||
(*state)[1][3] = (*state)[2][3];
|
||||
(*state)[2][3] = (*state)[3][3];
|
||||
(*state)[3][3] = temp;
|
||||
}
|
||||
|
||||
|
||||
// Cipher is the main function that encrypts the PlainText.
|
||||
static void Cipher(void)
|
||||
{
|
||||
uint8_t round = 0;
|
||||
|
||||
// Add the First round key to the state before starting the rounds.
|
||||
AddRoundKey(0);
|
||||
|
||||
// There will be Nr rounds.
|
||||
// The first Nr-1 rounds are identical.
|
||||
// These Nr-1 rounds are executed in the loop below.
|
||||
for (round = 1; round < Nr; ++round)
|
||||
{
|
||||
SubBytes();
|
||||
ShiftRows();
|
||||
MixColumns();
|
||||
AddRoundKey(round);
|
||||
}
|
||||
|
||||
// The last round is given below.
|
||||
// The MixColumns function is not here in the last round.
|
||||
SubBytes();
|
||||
ShiftRows();
|
||||
AddRoundKey(Nr);
|
||||
}
|
||||
|
||||
static void InvCipher(void)
|
||||
{
|
||||
uint8_t round=0;
|
||||
|
||||
// Add the First round key to the state before starting the rounds.
|
||||
AddRoundKey(Nr);
|
||||
|
||||
// There will be Nr rounds.
|
||||
// The first Nr-1 rounds are identical.
|
||||
// These Nr-1 rounds are executed in the loop below.
|
||||
for (round = (Nr - 1); round > 0; --round)
|
||||
{
|
||||
InvShiftRows();
|
||||
InvSubBytes();
|
||||
AddRoundKey(round);
|
||||
InvMixColumns();
|
||||
}
|
||||
|
||||
// The last round is given below.
|
||||
// The MixColumns function is not here in the last round.
|
||||
InvShiftRows();
|
||||
InvSubBytes();
|
||||
AddRoundKey(0);
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Public functions: */
|
||||
/*****************************************************************************/
|
||||
#if defined(AES_ECB) && (AES_ECB == 1)
|
||||
|
||||
|
||||
void AES_ECB_encrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key)
|
||||
{
|
||||
// Copy input to output, and work in-memory on output
|
||||
memcpy(output, input, length);
|
||||
state = (state_t*)output;
|
||||
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
|
||||
// The next function call encrypts the PlainText with the Key using AES algorithm.
|
||||
Cipher();
|
||||
|
||||
mpw_zero( RoundKey, keyExpSize );
|
||||
}
|
||||
|
||||
void AES_ECB_decrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key)
|
||||
{
|
||||
// Copy input to output, and work in-memory on output
|
||||
memcpy(output, input, length);
|
||||
state = (state_t*)output;
|
||||
|
||||
// The KeyExpansion routine must be called before encryption.
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
|
||||
InvCipher();
|
||||
|
||||
mpw_zero( RoundKey, keyExpSize );
|
||||
}
|
||||
|
||||
|
||||
#endif // #if defined(AES_ECB) && (AES_ECB == 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(AES_CBC) && (AES_CBC == 1)
|
||||
|
||||
|
||||
static void XorWithIv(uint8_t* buf)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < AES_BLOCKLEN; ++i) //WAS for(i = 0; i < KEYLEN; ++i) but the block in AES is always 128bit so 16 bytes!
|
||||
{
|
||||
buf[i] ^= Iv[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AES_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
|
||||
{
|
||||
uintptr_t i;
|
||||
uint8_t extra = (uint8_t)(length % AES_BLOCKLEN); /* Remaining bytes in the last non-full block */
|
||||
|
||||
// Skip the key expansion if key is passed as 0
|
||||
if (0 != key)
|
||||
{
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
}
|
||||
|
||||
if (iv != 0)
|
||||
{
|
||||
Iv = (uint8_t*)iv;
|
||||
}
|
||||
|
||||
for (i = 0; i < length; i += AES_BLOCKLEN)
|
||||
{
|
||||
XorWithIv(input);
|
||||
memcpy(output, input, AES_BLOCKLEN);
|
||||
state = (state_t*)output;
|
||||
Cipher();
|
||||
Iv = output;
|
||||
input += AES_BLOCKLEN;
|
||||
output += AES_BLOCKLEN;
|
||||
//printf("Step %d - %d", i/16, i);
|
||||
}
|
||||
|
||||
if (extra)
|
||||
{
|
||||
memcpy(output, input, extra);
|
||||
state = (state_t*)output;
|
||||
Cipher();
|
||||
}
|
||||
|
||||
mpw_zero( RoundKey, keyExpSize );
|
||||
}
|
||||
|
||||
void AES_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
|
||||
{
|
||||
uintptr_t i;
|
||||
uint8_t extra = (uint8_t)(length % AES_BLOCKLEN); /* Remaining bytes in the last non-full block */
|
||||
|
||||
// Skip the key expansion if key is passed as 0
|
||||
if (0 != key)
|
||||
{
|
||||
Key = key;
|
||||
KeyExpansion();
|
||||
}
|
||||
|
||||
// If iv is passed as 0, we continue to encrypt without re-setting the Iv
|
||||
if (iv != 0)
|
||||
{
|
||||
Iv = (uint8_t*)iv;
|
||||
}
|
||||
|
||||
for (i = 0; i < length; i += AES_BLOCKLEN)
|
||||
{
|
||||
memcpy(output, input, AES_BLOCKLEN);
|
||||
state = (state_t*)output;
|
||||
InvCipher();
|
||||
XorWithIv(output);
|
||||
Iv = input;
|
||||
input += AES_BLOCKLEN;
|
||||
output += AES_BLOCKLEN;
|
||||
}
|
||||
|
||||
if (extra)
|
||||
{
|
||||
memcpy(output, input, extra);
|
||||
state = (state_t*)output;
|
||||
InvCipher();
|
||||
}
|
||||
|
||||
mpw_zero( RoundKey, keyExpSize );
|
||||
}
|
||||
|
||||
#endif // #if defined(AES_CBC) && (AES_CBC == 1)
|
50
core/c/aes.h
50
core/c/aes.h
@ -1,50 +0,0 @@
|
||||
/*
|
||||
|
||||
Source: https://github.com/kokke/tiny-AES-c
|
||||
|
||||
This is an implementation of the AES algorithm, specifically ECB and CBC mode.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _AES_H_
|
||||
#define _AES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// #define the macros below to 1/0 to enable/disable the mode of operation.
|
||||
//
|
||||
// AES_CBC enables AES encryption in CBC-mode of operation.
|
||||
// AES_ECB enables the basic ECB 16-byte block algorithm. Both can be enabled simultaneously.
|
||||
|
||||
// The #ifndef-guard allows it to be configured before #include'ing or at compile time.
|
||||
#ifndef AES_CBC
|
||||
#define AES_CBC 1
|
||||
#endif
|
||||
|
||||
#ifndef AES_ECB
|
||||
#define AES_ECB 1
|
||||
#endif
|
||||
|
||||
#define AES_128 1
|
||||
//#define AES_192 1
|
||||
//#define AES_256 1
|
||||
#define AES_BLOCKLEN 16 //Block length in bytes AES is 128b block only
|
||||
|
||||
#if defined(AES_ECB) && (AES_ECB == 1)
|
||||
|
||||
void AES_ECB_encrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key);
|
||||
void AES_ECB_decrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key);
|
||||
|
||||
#endif // #if defined(AES_ECB) && (AES_ECB == !)
|
||||
|
||||
|
||||
#if defined(AES_CBC) && (AES_CBC == 1)
|
||||
|
||||
void AES_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, const uint32_t length, const uint8_t* key, const uint8_t* iv);
|
||||
void AES_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, const uint32_t length, const uint8_t* key, const uint8_t* iv);
|
||||
|
||||
#endif // #if defined(AES_CBC) && (AES_CBC == 1)
|
||||
|
||||
|
||||
#endif //_AES_H_
|
155
core/c/base64.c
155
core/c/base64.c
@ -1,155 +0,0 @@
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the Apache Group
|
||||
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||
*
|
||||
* 4. The names "Apache Server" and "Apache Group" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the Apache Group
|
||||
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
|
||||
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Group and was originally based
|
||||
* on public domain software written at the National Center for
|
||||
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
|
||||
* For more information on the Apache Group and the Apache HTTP server
|
||||
* project, please see <http://www.apache.org/>.
|
||||
*/
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
/* aaaack but it's fast and const should make it shared text page. */
|
||||
static const uint8_t b64ToBits[256] =
|
||||
{
|
||||
/* ASCII table */
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
||||
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
|
||||
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
|
||||
};
|
||||
|
||||
size_t mpw_base64_decode_max(const char *b64Text) {
|
||||
|
||||
register const uint8_t *b64Cursor = (uint8_t *)b64Text;
|
||||
while (b64ToBits[*(b64Cursor++)] <= 63);
|
||||
int b64Size = (int)(b64Cursor - (uint8_t *)b64Text) - 1;
|
||||
|
||||
// Every 4 b64 chars yield 3 plain bytes => len = 3 * ceil(b64Size / 4)
|
||||
return (size_t)(3 /*bytes*/ * ((b64Size + 4 /*chars*/ - 1) / 4 /*chars*/));
|
||||
}
|
||||
|
||||
int mpw_base64_decode(uint8_t *plainBuf, const char *b64Text) {
|
||||
|
||||
register const uint8_t *b64Cursor = (uint8_t *)b64Text;
|
||||
while (b64ToBits[*(b64Cursor++)] <= 63);
|
||||
int b64Remaining = (int)(b64Cursor - (uint8_t *)b64Text) - 1;
|
||||
|
||||
b64Cursor = (uint8_t *)b64Text;
|
||||
register uint8_t *plainCursor = plainBuf;
|
||||
while (b64Remaining > 4) {
|
||||
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
|
||||
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
|
||||
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[2]] << 6 | b64ToBits[b64Cursor[3]]);
|
||||
b64Cursor += 4;
|
||||
b64Remaining -= 4;
|
||||
}
|
||||
|
||||
/* Note: (b64Size == 1) would be an error, so just ingore that case */
|
||||
if (b64Remaining > 1)
|
||||
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
|
||||
if (b64Remaining > 2)
|
||||
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
|
||||
if (b64Remaining > 3)
|
||||
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[2]] << 6 | b64ToBits[b64Cursor[3]]);
|
||||
|
||||
return (int)(plainCursor - plainBuf);
|
||||
}
|
||||
|
||||
static const char basis_64[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
size_t mpw_base64_encode_max(size_t plainSize) {
|
||||
|
||||
// Every 3 plain bytes yield 4 b64 chars => len = 4 * ceil(plainSize / 3)
|
||||
return 4 /*chars*/ * (plainSize + 3 /*bytes*/ - 1) / 3 /*bytes*/;
|
||||
}
|
||||
|
||||
int mpw_base64_encode(char *b64Text, const uint8_t *plainBuf, size_t plainSize) {
|
||||
|
||||
size_t plainCursor = 0;
|
||||
char *b64Cursor = b64Text;
|
||||
for (; plainCursor < plainSize - 2; plainCursor += 3) {
|
||||
*b64Cursor++ = basis_64[((plainBuf[plainCursor] >> 2)) & 0x3F];
|
||||
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4) |
|
||||
((plainBuf[plainCursor + 1] & 0xF0) >> 4)];
|
||||
*b64Cursor++ = basis_64[((plainBuf[plainCursor + 1] & 0xF) << 2) |
|
||||
((plainBuf[plainCursor + 2] & 0xC0) >> 6)];
|
||||
*b64Cursor++ = basis_64[plainBuf[plainCursor + 2] & 0x3F];
|
||||
}
|
||||
if (plainCursor < plainSize) {
|
||||
*b64Cursor++ = basis_64[(plainBuf[plainCursor] >> 2) & 0x3F];
|
||||
if (plainCursor == (plainSize - 1)) {
|
||||
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4)];
|
||||
*b64Cursor++ = '=';
|
||||
}
|
||||
else {
|
||||
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4) |
|
||||
((plainBuf[plainCursor + 1] & 0xF0) >> 4)];
|
||||
*b64Cursor++ = basis_64[((plainBuf[plainCursor + 1] & 0xF) << 2)];
|
||||
}
|
||||
*b64Cursor++ = '=';
|
||||
}
|
||||
|
||||
*b64Cursor = '\0';
|
||||
return (int)(b64Cursor - b64Text);
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the Apache Group
|
||||
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||
*
|
||||
* 4. The names "Apache Server" and "Apache Group" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the Apache Group
|
||||
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
|
||||
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Group and was originally based
|
||||
* on public domain software written at the National Center for
|
||||
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
|
||||
* For more information on the Apache Group and the Apache HTTP server
|
||||
* project, please see <http://www.apache.org/>.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @return The amount of bytes needed to decode the given b64Text.
|
||||
*/
|
||||
size_t mpw_base64_decode_max(const char *b64Text);
|
||||
/** Decodes a base-64 encoded string into a plain byte buffer.
|
||||
* @param plainBuf a byte buffer, size should be at least mpw_base64_decode_max(b64Text)
|
||||
* @return The amount of bytes that were written to plainBuf.
|
||||
*/
|
||||
int mpw_base64_decode(uint8_t *plainBuf, const char *b64Text);
|
||||
|
||||
/**
|
||||
* @return The amount of characters needed to encode a plainBuf of the given size as base-64 (excluding the terminating NUL).
|
||||
*/
|
||||
size_t mpw_base64_encode_max(size_t plainSize);
|
||||
/** Encodes a plain byte buffer into a base-64 encoded string.
|
||||
* @param b64Text a character buffer, size should be at least mpw_base64_encode_max(plainSize) + 1
|
||||
* @return The amount of characters that were written to b64Text, excluding the terminating NUL.
|
||||
*/
|
||||
int mpw_base64_encode(char *b64Text, const uint8_t *plainBuf, size_t plainSize);
|
@ -1,226 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include "mpw-algorithm.h"
|
||||
#include "mpw-algorithm_v0.c"
|
||||
#include "mpw-algorithm_v1.c"
|
||||
#include "mpw-algorithm_v2.c"
|
||||
#include "mpw-algorithm_v3.c"
|
||||
|
||||
MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
if (fullName && !strlen( fullName ))
|
||||
fullName = NULL;
|
||||
if (masterPassword && !strlen( masterPassword ))
|
||||
masterPassword = NULL;
|
||||
|
||||
trc( "-- mpw_masterKey (algorithm: %u)", algorithmVersion );
|
||||
trc( "fullName: %s", fullName );
|
||||
trc( "masterPassword.id: %s", masterPassword? mpw_id_buf( masterPassword, strlen( masterPassword ) ): NULL );
|
||||
if (!fullName || !masterPassword)
|
||||
return NULL;
|
||||
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_masterKey_v0( fullName, masterPassword );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_masterKey_v1( fullName, masterPassword );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_masterKey_v2( fullName, masterPassword );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_masterKey_v3( fullName, masterPassword );
|
||||
default:
|
||||
err( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
MPSiteKey mpw_siteKey(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
if (keyContext && !strlen( keyContext ))
|
||||
keyContext = NULL;
|
||||
|
||||
trc( "-- mpw_siteKey (algorithm: %u)", algorithmVersion );
|
||||
trc( "siteName: %s", siteName );
|
||||
trc( "siteCounter: %d", siteCounter );
|
||||
trc( "keyPurpose: %d (%s)", keyPurpose, mpw_nameForPurpose( keyPurpose ) );
|
||||
trc( "keyContext: %s", keyContext );
|
||||
if (!masterKey || !siteName)
|
||||
return NULL;
|
||||
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_siteKey_v1( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_siteKey_v3( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
default:
|
||||
err( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_siteResult(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||
const MPResultType resultType, const char *resultParam,
|
||||
const MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
if (keyContext && !strlen( keyContext ))
|
||||
keyContext = NULL;
|
||||
if (resultParam && !strlen( resultParam ))
|
||||
resultParam = NULL;
|
||||
|
||||
MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
|
||||
if (!siteKey)
|
||||
return NULL;
|
||||
|
||||
trc( "-- mpw_siteResult (algorithm: %u)", algorithmVersion );
|
||||
trc( "resultType: %d (%s)", resultType, mpw_nameForType( resultType ) );
|
||||
trc( "resultParam: %s", resultParam );
|
||||
|
||||
char *sitePassword = NULL;
|
||||
if (resultType & MPResultTypeClassTemplate) {
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_sitePasswordFromTemplate_v0( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_sitePasswordFromTemplate_v3( masterKey, siteKey, resultType, resultParam );
|
||||
default:
|
||||
err( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if (resultType & MPResultTypeClassStateful) {
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_sitePasswordFromCrypt_v3( masterKey, siteKey, resultType, resultParam );
|
||||
default:
|
||||
err( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if (resultType & MPResultTypeClassDerive) {
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_sitePasswordFromDerive_v3( masterKey, siteKey, resultType, resultParam );
|
||||
default:
|
||||
err( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
err( "Unsupported password type: %d", resultType );
|
||||
}
|
||||
|
||||
return sitePassword;
|
||||
}
|
||||
|
||||
const char *mpw_siteState(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||
const MPResultType resultType, const char *resultParam,
|
||||
const MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
if (keyContext && !strlen( keyContext ))
|
||||
keyContext = NULL;
|
||||
if (resultParam && !strlen( resultParam ))
|
||||
resultParam = NULL;
|
||||
|
||||
MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
|
||||
if (!siteKey)
|
||||
return NULL;
|
||||
|
||||
trc( "-- mpw_siteState (algorithm: %u)", algorithmVersion );
|
||||
trc( "resultType: %d (%s)", resultType, mpw_nameForType( resultType ) );
|
||||
trc( "resultParam: %zu bytes = %s", sizeof( resultParam ), resultParam );
|
||||
if (!masterKey || !resultParam)
|
||||
return NULL;
|
||||
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_siteState_v0( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_siteState_v1( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_siteState_v2( masterKey, siteKey, resultType, resultParam );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_siteState_v3( masterKey, siteKey, resultType, resultParam );
|
||||
default:
|
||||
err( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
MPIdenticon mpw_identicon(const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *leftArm[] = { "╔", "╚", "╰", "═" };
|
||||
const char *rightArm[] = { "╗", "╝", "╯", "═" };
|
||||
const char *body[] = { "█", "░", "▒", "▓", "☺", "☻" };
|
||||
const char *accessory[] = {
|
||||
"◈", "◎", "◐", "◑", "◒", "◓", "☀", "☁", "☂", "☃", "", "★", "☆", "☎", "☏", "⎈", "⌂", "☘", "☢", "☣",
|
||||
"☕", "⌚", "⌛", "⏰", "⚡", "⛄", "⛅", "☔", "♔", "♕", "♖", "♗", "♘", "♙", "♚", "♛", "♜", "♝", "♞", "♟",
|
||||
"♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌"
|
||||
};
|
||||
|
||||
const uint8_t *identiconSeed = NULL;
|
||||
if (fullName && strlen( fullName ) && masterPassword && strlen( masterPassword ))
|
||||
identiconSeed = mpw_hash_hmac_sha256(
|
||||
(const uint8_t *)masterPassword, strlen( masterPassword ),
|
||||
(const uint8_t *)fullName, strlen( fullName ) );
|
||||
if (!identiconSeed)
|
||||
return (MPIdenticon){
|
||||
.leftArm = "",
|
||||
.body = "",
|
||||
.rightArm = "",
|
||||
.accessory = "",
|
||||
.color=0,
|
||||
};
|
||||
|
||||
MPIdenticon identicon = {
|
||||
.leftArm = leftArm[identiconSeed[0] % (sizeof( leftArm ) / sizeof( leftArm[0] ))],
|
||||
.body = body[identiconSeed[1] % (sizeof( body ) / sizeof( body[0] ))],
|
||||
.rightArm = rightArm[identiconSeed[2] % (sizeof( rightArm ) / sizeof( rightArm[0] ))],
|
||||
.accessory = accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
|
||||
.color = (uint8_t)(identiconSeed[4] % (MPIdenticonColorLast - MPIdenticonColorFirst + 1) + MPIdenticonColorFirst),
|
||||
};
|
||||
mpw_free( &identiconSeed, 32 );
|
||||
|
||||
return identicon;
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
// NOTE: mpw is currently NOT thread-safe.
|
||||
#include "mpw-types.h"
|
||||
|
||||
#ifndef _MPW_ALGORITHM_H
|
||||
#define _MPW_ALGORITHM_H
|
||||
|
||||
typedef mpw_enum( unsigned int, MPAlgorithmVersion ) {
|
||||
/** V0 did math with chars whose signedness was platform-dependent. */
|
||||
MPAlgorithmVersion0,
|
||||
/** V1 miscounted the byte-length of multi-byte site names. */
|
||||
MPAlgorithmVersion1,
|
||||
/** V2 miscounted the byte-length of multi-byte user names. */
|
||||
MPAlgorithmVersion2,
|
||||
/** V3 is the current version. */
|
||||
MPAlgorithmVersion3,
|
||||
|
||||
MPAlgorithmVersionCurrent = MPAlgorithmVersion3,
|
||||
MPAlgorithmVersionFirst = MPAlgorithmVersion0,
|
||||
MPAlgorithmVersionLast = MPAlgorithmVersion3,
|
||||
};
|
||||
|
||||
/** Derive the master key for a user based on their name and master password.
|
||||
* @return A new MPMasterKeySize-byte allocated buffer or NULL if an error occurred. */
|
||||
MPMasterKey mpw_masterKey(
|
||||
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
|
||||
|
||||
/** Derive the site key for a user's site from the given master key and site parameters.
|
||||
* @return A new MPSiteKeySize-byte allocated buffer or NULL if an error occurred. */
|
||||
MPSiteKey mpw_siteKey(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion);
|
||||
|
||||
/** Generate a site result token from the given parameters.
|
||||
* @param resultParam A parameter for the resultType. For stateful result types, the output of mpw_siteState.
|
||||
* @return A newly allocated string or NULL if an error occurred. */
|
||||
const char *mpw_siteResult(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||
const MPResultType resultType, const char *resultParam,
|
||||
const MPAlgorithmVersion algorithmVersion);
|
||||
|
||||
/** Encrypt a stateful site token for persistence.
|
||||
* @param resultParam A parameter for the resultType. For stateful result types, the desired mpw_siteResult.
|
||||
* @return A newly allocated string or NULL if an error occurred. */
|
||||
const char *mpw_siteState(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||
const MPResultType resultType, const char *resultParam,
|
||||
const MPAlgorithmVersion algorithmVersion);
|
||||
|
||||
/** @return A fingerprint for a user. */
|
||||
MPIdenticon mpw_identicon(const char *fullName, const char *masterPassword);
|
||||
|
||||
#endif // _MPW_ALGORITHM_H
|
@ -1,257 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "mpw-util.h"
|
||||
#include "base64.h"
|
||||
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
#define MP_otp_window 5 * 60 /* s */
|
||||
|
||||
// Algorithm version helpers.
|
||||
static const char *mpw_templateForType_v0(MPResultType type, uint16_t templateIndex) {
|
||||
|
||||
size_t count = 0;
|
||||
const char **templates = mpw_templatesForType( type, &count );
|
||||
char const *template = templates && count? templates[templateIndex % count]: NULL;
|
||||
free( templates );
|
||||
return template;
|
||||
}
|
||||
|
||||
static const char mpw_characterFromClass_v0(char characterClass, uint16_t classIndex) {
|
||||
|
||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||
if (!classCharacters)
|
||||
return '\0';
|
||||
|
||||
return classCharacters[classIndex % strlen( classCharacters )];
|
||||
}
|
||||
|
||||
// Algorithm version overrides.
|
||||
static MPMasterKey mpw_masterKey_v0(
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *keyScope = mpw_scopeForPurpose( MPKeyPurposeAuthentication );
|
||||
trc( "keyScope: %s", keyScope );
|
||||
|
||||
// Calculate the master key salt.
|
||||
trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
|
||||
keyScope, mpw_hex_l( (uint32_t)mpw_utf8_strlen( fullName ) ), fullName );
|
||||
size_t masterKeySaltSize = 0;
|
||||
uint8_t *masterKeySalt = NULL;
|
||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, keyScope );
|
||||
mpw_push_int( &masterKeySalt, &masterKeySaltSize, (uint32_t)mpw_utf8_strlen( fullName ) );
|
||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
||||
if (!masterKeySalt) {
|
||||
err( "Could not allocate master key salt: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => masterKeySalt.id: %s", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
||||
|
||||
// Calculate the master key.
|
||||
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )", MP_N, MP_r, MP_p );
|
||||
MPMasterKey masterKey = mpw_kdf_scrypt( MPMasterKeySize, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
||||
mpw_free( &masterKeySalt, masterKeySaltSize );
|
||||
if (!masterKey) {
|
||||
err( "Could not derive master key: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => masterKey.id: %s", mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
static MPSiteKey mpw_siteKey_v0(
|
||||
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
|
||||
MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
|
||||
trc( "keyScope: %s", keyScope );
|
||||
|
||||
// OTP counter value.
|
||||
if (siteCounter == MPCounterValueTOTP)
|
||||
siteCounter = ((uint32_t)time( NULL ) / MP_otp_window) * MP_otp_window;
|
||||
|
||||
// Calculate the site seed.
|
||||
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
|
||||
keyScope, mpw_hex_l( (uint32_t)mpw_utf8_strlen( siteName ) ), siteName, mpw_hex_l( siteCounter ),
|
||||
keyContext? mpw_hex_l( (uint32_t)mpw_utf8_strlen( keyContext ) ): NULL, keyContext );
|
||||
size_t siteSaltSize = 0;
|
||||
uint8_t *siteSalt = NULL;
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, (uint32_t)mpw_utf8_strlen( siteName ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, siteCounter );
|
||||
if (keyContext) {
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, (uint32_t)mpw_utf8_strlen( keyContext ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
|
||||
}
|
||||
if (!siteSalt) {
|
||||
err( "Could not allocate site salt: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => siteSalt.id: %s", mpw_id_buf( siteSalt, siteSaltSize ) );
|
||||
|
||||
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )",
|
||||
mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
|
||||
mpw_free( &siteSalt, siteSaltSize );
|
||||
if (!siteKey) {
|
||||
err( "Could not derive site key: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => siteKey.id: %s", mpw_id_buf( siteKey, MPSiteKeySize ) );
|
||||
|
||||
return siteKey;
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromTemplate_v0(
|
||||
MPMasterKey __unused masterKey, MPSiteKey siteKey, MPResultType resultType, const char __unused *resultParam) {
|
||||
|
||||
const char *_siteKey = (const char *)siteKey;
|
||||
|
||||
// Determine the template.
|
||||
uint16_t seedByte;
|
||||
mpw_uint16( (uint16_t)_siteKey[0], (uint8_t *)&seedByte );
|
||||
const char *template = mpw_templateForType_v0( resultType, seedByte );
|
||||
trc( "template: %u => %s", seedByte, template );
|
||||
if (!template)
|
||||
return NULL;
|
||||
if (strlen( template ) > MPSiteKeySize) {
|
||||
err( "Template too long for password seed: %zu", strlen( template ) );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Encode the password from the seed using the template.
|
||||
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
||||
for (size_t c = 0; c < strlen( template ); ++c) {
|
||||
mpw_uint16( (uint16_t)_siteKey[c + 1], (uint8_t *)&seedByte );
|
||||
sitePassword[c] = mpw_characterFromClass_v0( template[c], seedByte );
|
||||
trc( " - class: %c, index: %5u (0x%02hX) => character: %c",
|
||||
template[c], seedByte, seedByte, sitePassword[c] );
|
||||
}
|
||||
trc( " => password: %s", sitePassword );
|
||||
|
||||
return sitePassword;
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromCrypt_v0(
|
||||
MPMasterKey masterKey, MPSiteKey __unused siteKey, MPResultType __unused resultType, const char *cipherText) {
|
||||
|
||||
if (!cipherText) {
|
||||
err( "Missing encrypted state." );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Base64-decode
|
||||
uint8_t *cipherBuf = calloc( 1, mpw_base64_decode_max( cipherText ) );
|
||||
size_t bufSize = (size_t)mpw_base64_decode( cipherBuf, cipherText ), cipherBufSize = bufSize;
|
||||
if ((int)bufSize < 0) {
|
||||
err( "Base64 decoding error." );
|
||||
mpw_free( &cipherBuf, mpw_base64_decode_max( cipherText ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( "b64 decoded: %zu bytes = %s", bufSize, mpw_hex( cipherBuf, bufSize ) );
|
||||
|
||||
// Decrypt
|
||||
const uint8_t *plainBytes = mpw_aes_decrypt( masterKey, MPMasterKeySize, cipherBuf, &bufSize );
|
||||
mpw_free( &cipherBuf, cipherBufSize );
|
||||
const char *plainText = mpw_strndup( (char *)plainBytes, bufSize );
|
||||
mpw_free( &plainBytes, bufSize );
|
||||
if (!plainText)
|
||||
err( "AES decryption error: %s", strerror( errno ) );
|
||||
trc( "decrypted -> plainText: %zu bytes = %s = %s", strlen( plainText ), plainText, mpw_hex( plainText, strlen( plainText ) ) );
|
||||
|
||||
return plainText;
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromDerive_v0(
|
||||
MPMasterKey __unused masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
|
||||
|
||||
switch (resultType) {
|
||||
case MPResultTypeDeriveKey: {
|
||||
if (!resultParam) {
|
||||
err( "Missing key size parameter." );
|
||||
return NULL;
|
||||
}
|
||||
int resultParamInt = atoi( resultParam );
|
||||
if (!resultParamInt)
|
||||
resultParamInt = 512;
|
||||
if (resultParamInt < 128 || resultParamInt > 512 || resultParamInt % 8 != 0) {
|
||||
err( "Parameter is not a valid key size (should be 128 - 512): %s", resultParam );
|
||||
return NULL;
|
||||
}
|
||||
uint16_t keySize = (uint16_t)(resultParamInt / 8);
|
||||
trc( "keySize: %u", keySize );
|
||||
|
||||
// Derive key
|
||||
const uint8_t *resultKey = mpw_kdf_blake2b( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL );
|
||||
if (!resultKey) {
|
||||
err( "Could not derive result key: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Base64-encode
|
||||
size_t b64Max = mpw_base64_encode_max( keySize );
|
||||
char *b64Key = calloc( 1, b64Max + 1 );
|
||||
if (mpw_base64_encode( b64Key, resultKey, keySize ) < 0) {
|
||||
err( "Base64 encoding error." );
|
||||
mpw_free_string( &b64Key );
|
||||
}
|
||||
else
|
||||
trc( "b64 encoded -> key: %s", b64Key );
|
||||
mpw_free( &resultKey, keySize );
|
||||
|
||||
return b64Key;
|
||||
}
|
||||
default:
|
||||
err( "Unsupported derived password type: %d", resultType );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *mpw_siteState_v0(
|
||||
MPMasterKey masterKey, MPSiteKey __unused siteKey, MPResultType __unused resultType, const char *plainText) {
|
||||
|
||||
// Encrypt
|
||||
size_t bufSize = strlen( plainText );
|
||||
const uint8_t *cipherBuf = mpw_aes_encrypt( masterKey, MPMasterKeySize, (const uint8_t *)plainText, &bufSize );
|
||||
if (!cipherBuf) {
|
||||
err( "AES encryption error: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( "cipherBuf: %zu bytes = %s", bufSize, mpw_hex( cipherBuf, bufSize ) );
|
||||
|
||||
// Base64-encode
|
||||
size_t b64Max = mpw_base64_encode_max( bufSize );
|
||||
char *cipherText = calloc( 1, b64Max + 1 );
|
||||
if (mpw_base64_encode( cipherText, cipherBuf, bufSize ) < 0) {
|
||||
err( "Base64 encoding error." );
|
||||
mpw_free_string( &cipherText );
|
||||
}
|
||||
else
|
||||
trc( "b64 encoded -> cipherText: %s", cipherText );
|
||||
mpw_free( &cipherBuf, bufSize );
|
||||
|
||||
return cipherText;
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
#define MP_otp_window 5 * 60 /* s */
|
||||
|
||||
// Inherited functions.
|
||||
MPMasterKey mpw_masterKey_v0(
|
||||
const char *fullName, const char *masterPassword);
|
||||
MPSiteKey mpw_siteKey_v0(
|
||||
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
|
||||
MPKeyPurpose keyPurpose, const char *keyContext);
|
||||
const char *mpw_sitePasswordFromCrypt_v0(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText);
|
||||
const char *mpw_sitePasswordFromDerive_v0(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
|
||||
const char *mpw_siteState_v0(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state);
|
||||
|
||||
// Algorithm version overrides.
|
||||
static MPMasterKey mpw_masterKey_v1(
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
return mpw_masterKey_v0( fullName, masterPassword );
|
||||
}
|
||||
|
||||
static MPSiteKey mpw_siteKey_v1(
|
||||
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
|
||||
MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromTemplate_v1(
|
||||
MPMasterKey __unused masterKey, MPSiteKey siteKey, MPResultType resultType, const char __unused *resultParam) {
|
||||
|
||||
// Determine the template.
|
||||
uint8_t seedByte = siteKey[0];
|
||||
const char *template = mpw_templateForType( resultType, seedByte );
|
||||
trc( "template: %u => %s", seedByte, template );
|
||||
if (!template)
|
||||
return NULL;
|
||||
if (strlen( template ) > MPSiteKeySize) {
|
||||
err( "Template too long for password seed: %zu", strlen( template ) );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Encode the password from the seed using the template.
|
||||
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
||||
for (size_t c = 0; c < strlen( template ); ++c) {
|
||||
seedByte = siteKey[c + 1];
|
||||
sitePassword[c] = mpw_characterFromClass( template[c], seedByte );
|
||||
trc( " - class: %c, index: %3u (0x%02hhX) => character: %c",
|
||||
template[c], seedByte, seedByte, sitePassword[c] );
|
||||
}
|
||||
trc( " => password: %s", sitePassword );
|
||||
|
||||
return sitePassword;
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromCrypt_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText) {
|
||||
|
||||
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, cipherText );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromDerive_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_siteState_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state) {
|
||||
|
||||
return mpw_siteState_v0( masterKey, siteKey, resultType, state );
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
#define MP_otp_window 5 * 60 /* s */
|
||||
|
||||
// Inherited functions.
|
||||
MPMasterKey mpw_masterKey_v1(
|
||||
const char *fullName, const char *masterPassword);
|
||||
const char *mpw_sitePasswordFromTemplate_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
|
||||
const char *mpw_sitePasswordFromCrypt_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText);
|
||||
const char *mpw_sitePasswordFromDerive_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
|
||||
const char *mpw_siteState_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state);
|
||||
|
||||
// Algorithm version overrides.
|
||||
static MPMasterKey mpw_masterKey_v2(
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
return mpw_masterKey_v1( fullName, masterPassword );
|
||||
}
|
||||
|
||||
static MPSiteKey mpw_siteKey_v2(
|
||||
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
|
||||
MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
|
||||
trc( "keyScope: %s", keyScope );
|
||||
|
||||
// OTP counter value.
|
||||
if (siteCounter == MPCounterValueTOTP)
|
||||
siteCounter = ((uint32_t)time( NULL ) / MP_otp_window) * MP_otp_window;
|
||||
|
||||
// Calculate the site seed.
|
||||
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
|
||||
keyScope, mpw_hex_l( (uint32_t)strlen( siteName ) ), siteName, mpw_hex_l( siteCounter ),
|
||||
keyContext? mpw_hex_l( (uint32_t)strlen( keyContext ) ): NULL, keyContext );
|
||||
size_t siteSaltSize = 0;
|
||||
uint8_t *siteSalt = NULL;
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, (uint32_t)strlen( siteName ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, siteCounter );
|
||||
if (keyContext) {
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, (uint32_t)strlen( keyContext ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
|
||||
}
|
||||
if (!siteSalt) {
|
||||
err( "Could not allocate site salt: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => siteSalt.id: %s", mpw_id_buf( siteSalt, siteSaltSize ) );
|
||||
|
||||
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )",
|
||||
mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
|
||||
mpw_free( &siteSalt, siteSaltSize );
|
||||
if (!siteKey) {
|
||||
err( "Could not derive site key: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => siteKey.id: %s", mpw_id_buf( siteKey, MPSiteKeySize ) );
|
||||
|
||||
return siteKey;
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromTemplate_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromCrypt_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText) {
|
||||
|
||||
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, cipherText );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromDerive_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_siteState_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state) {
|
||||
|
||||
return mpw_siteState_v1( masterKey, siteKey, resultType, state );
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
#define MP_otp_window 5 * 60 /* s */
|
||||
|
||||
// Inherited functions.
|
||||
MPSiteKey mpw_siteKey_v2(
|
||||
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
|
||||
MPKeyPurpose keyPurpose, const char *keyContext);
|
||||
const char *mpw_sitePasswordFromTemplate_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
|
||||
const char *mpw_sitePasswordFromCrypt_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText);
|
||||
const char *mpw_sitePasswordFromDerive_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
|
||||
const char *mpw_siteState_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state);
|
||||
|
||||
// Algorithm version overrides.
|
||||
static MPMasterKey mpw_masterKey_v3(
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *keyScope = mpw_scopeForPurpose( MPKeyPurposeAuthentication );
|
||||
trc( "keyScope: %s", keyScope );
|
||||
|
||||
// Calculate the master key salt.
|
||||
trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
|
||||
keyScope, mpw_hex_l( (uint32_t)strlen( fullName ) ), fullName );
|
||||
size_t masterKeySaltSize = 0;
|
||||
uint8_t *masterKeySalt = NULL;
|
||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, keyScope );
|
||||
mpw_push_int( &masterKeySalt, &masterKeySaltSize, (uint32_t)strlen( fullName ) );
|
||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
||||
if (!masterKeySalt) {
|
||||
err( "Could not allocate master key salt: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => masterKeySalt.id: %s", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
||||
|
||||
// Calculate the master key.
|
||||
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )", MP_N, MP_r, MP_p );
|
||||
MPMasterKey masterKey = mpw_kdf_scrypt( MPMasterKeySize, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
||||
mpw_free( &masterKeySalt, masterKeySaltSize );
|
||||
if (!masterKey) {
|
||||
err( "Could not derive master key: %s", strerror( errno ) );
|
||||
return NULL;
|
||||
}
|
||||
trc( " => masterKey.id: %s", mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
static MPSiteKey mpw_siteKey_v3(
|
||||
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
|
||||
MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromTemplate_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromCrypt_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText) {
|
||||
|
||||
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, cipherText );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromDerive_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_siteState_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state) {
|
||||
|
||||
return mpw_siteState_v2( masterKey, siteKey, resultType, state );
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "mpw-marshal-util.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
char *mpw_get_token(const char **in, const char *eol, char *delim) {
|
||||
|
||||
// Skip leading spaces.
|
||||
for (; **in == ' '; ++*in);
|
||||
|
||||
// Find characters up to the first delim.
|
||||
size_t len = strcspn( *in, delim );
|
||||
char *token = len && len <= (size_t)(eol - *in)? mpw_strndup( *in, len ): NULL;
|
||||
|
||||
// Advance past the delimitor.
|
||||
*in = min( eol, *in + len + 1 );
|
||||
return token;
|
||||
}
|
||||
|
||||
time_t mpw_mktime(
|
||||
const char *time) {
|
||||
|
||||
struct tm tm = { .tm_isdst = -1 };
|
||||
if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ",
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
|
||||
&tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) {
|
||||
tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900
|
||||
tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1
|
||||
return mktime( &tm );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if MPW_JSON
|
||||
json_object *mpw_get_json_section(
|
||||
json_object *obj, const char *section) {
|
||||
|
||||
json_object *json_value = obj;
|
||||
char *sectionTokenizer = mpw_strdup( section ), *sectionToken = sectionTokenizer;
|
||||
for (sectionToken = strtok( sectionToken, "." ); sectionToken; sectionToken = strtok( NULL, "." ))
|
||||
if (!json_object_object_get_ex( json_value, sectionToken, &json_value ) || !json_value) {
|
||||
trc( "While resolving: %s: Missing value for: %s", section, sectionToken );
|
||||
json_value = NULL;
|
||||
break;
|
||||
}
|
||||
free( sectionTokenizer );
|
||||
|
||||
return json_value;
|
||||
}
|
||||
|
||||
const char *mpw_get_json_string(
|
||||
json_object *obj, const char *section, const char *defaultValue) {
|
||||
|
||||
json_object *json_value = mpw_get_json_section( obj, section );
|
||||
if (!json_value)
|
||||
return defaultValue;
|
||||
|
||||
return json_object_get_string( json_value );
|
||||
}
|
||||
|
||||
int64_t mpw_get_json_int(
|
||||
json_object *obj, const char *section, int64_t defaultValue) {
|
||||
|
||||
json_object *json_value = mpw_get_json_section( obj, section );
|
||||
if (!json_value)
|
||||
return defaultValue;
|
||||
|
||||
return json_object_get_int64( json_value );
|
||||
}
|
||||
|
||||
bool mpw_get_json_boolean(
|
||||
json_object *obj, const char *section, bool defaultValue) {
|
||||
|
||||
json_object *json_value = mpw_get_json_section( obj, section );
|
||||
if (!json_value)
|
||||
return defaultValue;
|
||||
|
||||
return json_object_get_boolean( json_value ) == TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool mpw_update_masterKey(MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
if (*masterKeyAlgorithm != targetKeyAlgorithm) {
|
||||
mpw_free( masterKey, MPMasterKeySize );
|
||||
*masterKeyAlgorithm = targetKeyAlgorithm;
|
||||
*masterKey = mpw_masterKey( fullName, masterPassword, *masterKeyAlgorithm );
|
||||
if (!*masterKey) {
|
||||
err( "Couldn't derive master key for user %s, algorithm %d.", fullName, *masterKeyAlgorithm );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#ifndef _MPW_MARSHAL_UTIL_H
|
||||
#define _MPW_MARSHAL_UTIL_H
|
||||
|
||||
#include <time.h>
|
||||
#if MPW_JSON
|
||||
#include "json-c/json.h"
|
||||
#endif
|
||||
|
||||
#include "mpw-algorithm.h"
|
||||
|
||||
/// Type parsing.
|
||||
|
||||
/** Get a token from a string by searching until the first character in delim, no farther than eol.
|
||||
* 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(
|
||||
const char **in, const char *eol, char *delim);
|
||||
/** Convert an RFC 3339 time string into epoch time. */
|
||||
time_t mpw_mktime(
|
||||
const char *time);
|
||||
|
||||
/// JSON parsing.
|
||||
|
||||
#if MPW_JSON
|
||||
/** Search for a JSON child object in a JSON object tree.
|
||||
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||
* @return A new JSON object or NULL if one of the section's object keys was not found in the source object's tree. */
|
||||
json_object *mpw_get_json_section(
|
||||
json_object *obj, const char *section);
|
||||
/** Search for a string in a JSON object tree.
|
||||
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||
* @return A new string or defaultValue if one of the section's object keys was not found in the source object's tree. */
|
||||
const char *mpw_get_json_string(
|
||||
json_object *obj, const char *section, const char *defaultValue);
|
||||
/** Search for an integer in a JSON object tree.
|
||||
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||
* @return The integer value or defaultValue if one of the section's object keys was not found in the source object's tree. */
|
||||
int64_t mpw_get_json_int(
|
||||
json_object *obj, const char *section, int64_t defaultValue);
|
||||
/** Search for a boolean in a JSON object tree.
|
||||
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||
* @return The boolean value or defaultValue if one of the section's object keys was not found in the source object's tree. */
|
||||
bool mpw_get_json_boolean(
|
||||
json_object *obj, const char *section, bool defaultValue);
|
||||
#endif
|
||||
|
||||
/// mpw.
|
||||
|
||||
/** Calculate a master key if the target master key algorithm is different from the given master key algorithm.
|
||||
* @return false if an error occurred during the derivation of the master key. */
|
||||
bool mpw_update_masterKey(
|
||||
MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
|
||||
const char *fullName, const char *masterPassword);
|
||||
|
||||
#endif // _MPW_MARSHAL_UTIL_H
|
@ -1,924 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mpw-marshal.h"
|
||||
#include "mpw-util.h"
|
||||
#include "mpw-marshal-util.h"
|
||||
|
||||
MPMarshalledUser *mpw_marshal_user(
|
||||
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
MPMarshalledUser *user;
|
||||
if (!fullName || !masterPassword || !(user = malloc( sizeof( MPMarshalledUser ) )))
|
||||
return NULL;
|
||||
|
||||
*user = (MPMarshalledUser){
|
||||
.fullName = mpw_strdup( fullName ),
|
||||
.masterPassword = mpw_strdup( masterPassword ),
|
||||
.algorithm = algorithmVersion,
|
||||
.redacted = true,
|
||||
|
||||
.avatar = 0,
|
||||
.defaultType = MPResultTypeDefault,
|
||||
.lastUsed = 0,
|
||||
|
||||
.sites_count = 0,
|
||||
.sites = NULL,
|
||||
};
|
||||
return user;
|
||||
}
|
||||
|
||||
MPMarshalledSite *mpw_marshal_site(
|
||||
MPMarshalledUser *user, const char *siteName, const MPResultType resultType,
|
||||
const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
if (!siteName || !mpw_realloc( &user->sites, NULL, sizeof( MPMarshalledSite ) * ++user->sites_count ))
|
||||
return NULL;
|
||||
|
||||
MPMarshalledSite *site = &user->sites[user->sites_count - 1];
|
||||
*site = (MPMarshalledSite){
|
||||
.name = mpw_strdup( siteName ),
|
||||
.content = NULL,
|
||||
.type = resultType,
|
||||
.counter = siteCounter,
|
||||
.algorithm = algorithmVersion,
|
||||
|
||||
.loginContent = NULL,
|
||||
.loginType = MPResultTypeTemplateName,
|
||||
|
||||
.url = NULL,
|
||||
.uses = 0,
|
||||
.lastUsed = 0,
|
||||
|
||||
.questions_count = 0,
|
||||
.questions = NULL,
|
||||
};
|
||||
return site;
|
||||
}
|
||||
|
||||
MPMarshalledQuestion *mpw_marshal_question(
|
||||
MPMarshalledSite *site, const char *keyword) {
|
||||
|
||||
if (!mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count ))
|
||||
return NULL;
|
||||
if (!keyword)
|
||||
keyword = "";
|
||||
|
||||
MPMarshalledQuestion *question = &site->questions[site->questions_count - 1];
|
||||
*question = (MPMarshalledQuestion){
|
||||
.keyword = mpw_strdup( keyword ),
|
||||
.content = NULL,
|
||||
.type = MPResultTypeTemplatePhrase,
|
||||
};
|
||||
return question;
|
||||
}
|
||||
|
||||
bool mpw_marshal_info_free(
|
||||
MPMarshalInfo **info) {
|
||||
|
||||
if (!info || !*info)
|
||||
return true;
|
||||
|
||||
bool success = true;
|
||||
success &= mpw_free_strings( &(*info)->fullName, &(*info)->keyID, NULL );
|
||||
success &= mpw_free( info, sizeof( MPMarshalInfo ) );
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mpw_marshal_free(
|
||||
MPMarshalledUser **user) {
|
||||
|
||||
if (!user || !*user)
|
||||
return true;
|
||||
|
||||
bool success = true;
|
||||
success &= mpw_free_strings( &(*user)->fullName, &(*user)->masterPassword, NULL );
|
||||
|
||||
for (size_t s = 0; s < (*user)->sites_count; ++s) {
|
||||
MPMarshalledSite *site = &(*user)->sites[s];
|
||||
success &= mpw_free_strings( &site->name, &site->content, &site->loginContent, &site->url, NULL );
|
||||
|
||||
for (size_t q = 0; q < site->questions_count; ++q) {
|
||||
MPMarshalledQuestion *question = &site->questions[q];
|
||||
success &= mpw_free_strings( &question->keyword, &question->content, NULL );
|
||||
}
|
||||
success &= mpw_free( &site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count );
|
||||
}
|
||||
|
||||
success &= mpw_free( &(*user)->sites, sizeof( MPMarshalledSite ) * (*user)->sites_count );
|
||||
success &= mpw_free( user, sizeof( MPMarshalledUser ) );
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool mpw_marshal_write_flat(
|
||||
char **out, const MPMarshalledUser *user, MPMarshalError *error) {
|
||||
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
|
||||
if (!user->fullName || !strlen( user->fullName )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
|
||||
return false;
|
||||
}
|
||||
if (!user->masterPassword || !strlen( user->masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Missing master password." };
|
||||
return false;
|
||||
}
|
||||
MPMasterKey masterKey = NULL;
|
||||
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return false;
|
||||
}
|
||||
|
||||
mpw_string_pushf( out, "# Master Password site export\n" );
|
||||
if (user->redacted)
|
||||
mpw_string_pushf( out, "# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n" );
|
||||
else
|
||||
mpw_string_pushf( out, "# Export of site names and passwords in clear-text.\n" );
|
||||
mpw_string_pushf( out, "# \n" );
|
||||
mpw_string_pushf( out, "##\n" );
|
||||
mpw_string_pushf( out, "# Format: %d\n", 1 );
|
||||
|
||||
char dateString[21];
|
||||
time_t now = time( NULL );
|
||||
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
|
||||
mpw_string_pushf( out, "# Date: %s\n", dateString );
|
||||
mpw_string_pushf( out, "# User Name: %s\n", user->fullName );
|
||||
mpw_string_pushf( out, "# Full Name: %s\n", user->fullName );
|
||||
mpw_string_pushf( out, "# Avatar: %u\n", user->avatar );
|
||||
mpw_string_pushf( out, "# Key ID: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||
mpw_string_pushf( out, "# Algorithm: %d\n", user->algorithm );
|
||||
mpw_string_pushf( out, "# Default Type: %d\n", user->defaultType );
|
||||
mpw_string_pushf( out, "# Passwords: %s\n", user->redacted? "PROTECTED": "VISIBLE" );
|
||||
mpw_string_pushf( out, "##\n" );
|
||||
mpw_string_pushf( out, "#\n" );
|
||||
mpw_string_pushf( out, "# Last Times Password Login\t Site\tSite\n" );
|
||||
mpw_string_pushf( out, "# used used type name\t name\tpassword\n" );
|
||||
|
||||
// Sites.
|
||||
for (size_t s = 0; s < user->sites_count; ++s) {
|
||||
MPMarshalledSite *site = &user->sites[s];
|
||||
if (!site->name || !strlen( site->name ))
|
||||
continue;
|
||||
|
||||
const char *content = NULL, *loginContent = NULL;
|
||||
if (!user->redacted) {
|
||||
// Clear Text
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return false;
|
||||
}
|
||||
|
||||
content = mpw_siteResult( masterKey, site->name, site->counter,
|
||||
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
|
||||
loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
|
||||
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
|
||||
}
|
||||
else {
|
||||
// Redacted
|
||||
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
|
||||
content = mpw_strdup( site->content );
|
||||
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
|
||||
loginContent = mpw_strdup( site->loginContent );
|
||||
}
|
||||
|
||||
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
|
||||
mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n",
|
||||
dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter,
|
||||
loginContent?: "", site->name, content?: "" );
|
||||
mpw_free_strings( &content, &loginContent, NULL );
|
||||
}
|
||||
mpw_free( &masterKey, MPMasterKeySize );
|
||||
|
||||
*error = (MPMarshalError){ .type = MPMarshalSuccess };
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MPW_JSON
|
||||
static bool mpw_marshal_write_json(
|
||||
char **out, const MPMarshalledUser *user, MPMarshalError *error) {
|
||||
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
|
||||
if (!user->fullName || !strlen( user->fullName )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
|
||||
return false;
|
||||
}
|
||||
if (!user->masterPassword || !strlen( user->masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Missing master password." };
|
||||
return false;
|
||||
}
|
||||
MPMasterKey masterKey = NULL;
|
||||
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return false;
|
||||
}
|
||||
|
||||
// Section: "export"
|
||||
json_object *json_file = json_object_new_object();
|
||||
json_object *json_export = json_object_new_object();
|
||||
json_object_object_add( json_file, "export", json_export );
|
||||
json_object_object_add( json_export, "format", json_object_new_int( 1 ) );
|
||||
json_object_object_add( json_export, "redacted", json_object_new_boolean( user->redacted ) );
|
||||
|
||||
char dateString[21];
|
||||
time_t now = time( NULL );
|
||||
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
|
||||
json_object_object_add( json_export, "date", json_object_new_string( dateString ) );
|
||||
|
||||
// Section: "user"
|
||||
json_object *json_user = json_object_new_object();
|
||||
json_object_object_add( json_file, "user", json_user );
|
||||
json_object_object_add( json_user, "avatar", json_object_new_int( (int32_t)user->avatar ) );
|
||||
json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) );
|
||||
|
||||
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) ))
|
||||
json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) );
|
||||
json_object_object_add( json_user, "key_id", json_object_new_string( mpw_id_buf( masterKey, MPMasterKeySize ) ) );
|
||||
|
||||
json_object_object_add( json_user, "algorithm", json_object_new_int( (int32_t)user->algorithm ) );
|
||||
json_object_object_add( json_user, "default_type", json_object_new_int( (int32_t)user->defaultType ) );
|
||||
|
||||
// Section "sites"
|
||||
json_object *json_sites = json_object_new_object();
|
||||
json_object_object_add( json_file, "sites", json_sites );
|
||||
for (size_t s = 0; s < user->sites_count; ++s) {
|
||||
MPMarshalledSite *site = &user->sites[s];
|
||||
if (!site->name || !strlen( site->name ))
|
||||
continue;
|
||||
|
||||
const char *content = NULL, *loginContent = NULL;
|
||||
if (!user->redacted) {
|
||||
// Clear Text
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return false;
|
||||
}
|
||||
|
||||
content = mpw_siteResult( masterKey, site->name, site->counter,
|
||||
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
|
||||
loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
|
||||
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
|
||||
}
|
||||
else {
|
||||
// Redacted
|
||||
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
|
||||
content = mpw_strdup( site->content );
|
||||
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
|
||||
loginContent = mpw_strdup( site->loginContent );
|
||||
}
|
||||
|
||||
json_object *json_site = json_object_new_object();
|
||||
json_object_object_add( json_sites, site->name, json_site );
|
||||
json_object_object_add( json_site, "type", json_object_new_int( (int32_t)site->type ) );
|
||||
json_object_object_add( json_site, "counter", json_object_new_int( (int32_t)site->counter ) );
|
||||
json_object_object_add( json_site, "algorithm", json_object_new_int( (int32_t)site->algorithm ) );
|
||||
if (content)
|
||||
json_object_object_add( json_site, "password", json_object_new_string( content ) );
|
||||
if (loginContent)
|
||||
json_object_object_add( json_site, "login_name", json_object_new_string( loginContent ) );
|
||||
json_object_object_add( json_site, "login_type", json_object_new_int( (int32_t)site->loginType ) );
|
||||
|
||||
json_object_object_add( json_site, "uses", json_object_new_int( (int32_t)site->uses ) );
|
||||
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
|
||||
json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) );
|
||||
|
||||
json_object *json_site_questions = json_object_new_object();
|
||||
json_object_object_add( json_site, "questions", json_site_questions );
|
||||
for (size_t q = 0; q < site->questions_count; ++q) {
|
||||
MPMarshalledQuestion *question = &site->questions[q];
|
||||
if (!question->keyword)
|
||||
continue;
|
||||
|
||||
json_object *json_site_question = json_object_new_object();
|
||||
json_object_object_add( json_site_questions, question->keyword, json_site_question );
|
||||
json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) );
|
||||
|
||||
if (!user->redacted) {
|
||||
// Clear Text
|
||||
const char *answerContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
|
||||
MPKeyPurposeRecovery, question->keyword, question->type, question->content, site->algorithm );
|
||||
json_object_object_add( json_site_question, "answer", json_object_new_string( answerContent ) );
|
||||
}
|
||||
else {
|
||||
// Redacted
|
||||
if (site->type & MPSiteFeatureExportContent && question->content && strlen( question->content ))
|
||||
json_object_object_add( json_site_question, "answer", json_object_new_string( question->content ) );
|
||||
}
|
||||
}
|
||||
|
||||
json_object *json_site_mpw = json_object_new_object();
|
||||
json_object_object_add( json_site, "_ext_mpw", json_site_mpw );
|
||||
if (site->url)
|
||||
json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) );
|
||||
|
||||
mpw_free_strings( &content, &loginContent, NULL );
|
||||
}
|
||||
|
||||
mpw_string_pushf( out, "%s\n", json_object_to_json_string_ext( json_file, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED ) );
|
||||
mpw_free( &masterKey, MPMasterKeySize );
|
||||
json_object_put( json_file );
|
||||
|
||||
*error = (MPMarshalError){ .type = MPMarshalSuccess };
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool mpw_marshal_write(
|
||||
char **out, const MPMarshalFormat outFormat, const MPMarshalledUser *user, MPMarshalError *error) {
|
||||
|
||||
switch (outFormat) {
|
||||
case MPMarshalFormatNone:
|
||||
*error = (MPMarshalError){ .type = MPMarshalSuccess };
|
||||
return false;
|
||||
case MPMarshalFormatFlat:
|
||||
return mpw_marshal_write_flat( out, user, error );
|
||||
#if MPW_JSON
|
||||
case MPMarshalFormatJSON:
|
||||
return mpw_marshal_write_json( out, user, error );
|
||||
#endif
|
||||
default:
|
||||
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void mpw_marshal_read_flat_info(
|
||||
const char *in, MPMarshalInfo *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 = mpw_strdup( headerValue );
|
||||
if (strcmp( headerName, "Key ID" ) == 0)
|
||||
info->keyID = mpw_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_strings( &headerName, &headerValue, NULL );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MPMarshalledUser *mpw_marshal_read_flat(
|
||||
const char *in, const char *masterPassword, MPMarshalError *error) {
|
||||
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
|
||||
if (!in || !strlen( in )) {
|
||||
error->type = MPMarshalErrorStructure;
|
||||
error->description = mpw_str( "No input data." );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Parse import data.
|
||||
MPMasterKey masterKey = NULL;
|
||||
MPMarshalledUser *user = NULL;
|
||||
unsigned int format = 0, avatar = 0;
|
||||
char *fullName = NULL, *keyID = NULL;
|
||||
MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1;
|
||||
MPResultType defaultType = MPResultTypeDefault;
|
||||
bool headerStarted = false, headerEnded = false, importRedacted = 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 (headerEnded)
|
||||
// Comment after header
|
||||
continue;
|
||||
if (*positionInLine == '#') {
|
||||
// ## ends header
|
||||
headerEnded = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Header
|
||||
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
|
||||
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
|
||||
if (!headerName || !headerValue) {
|
||||
error->type = MPMarshalErrorStructure;
|
||||
error->description = mpw_str( "Invalid header: %s", mpw_strndup( positionInLine, (size_t)(endOfLine - positionInLine) ) );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strcmp( headerName, "Format" ) == 0)
|
||||
format = (unsigned int)atoi( headerValue );
|
||||
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
|
||||
fullName = mpw_strdup( headerValue );
|
||||
if (strcmp( headerName, "Avatar" ) == 0)
|
||||
avatar = (unsigned int)atoi( headerValue );
|
||||
if (strcmp( headerName, "Key ID" ) == 0)
|
||||
keyID = mpw_strdup( headerValue );
|
||||
if (strcmp( headerName, "Algorithm" ) == 0) {
|
||||
int value = atoi( headerValue );
|
||||
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) };
|
||||
return NULL;
|
||||
}
|
||||
algorithm = (MPAlgorithmVersion)value;
|
||||
}
|
||||
if (strcmp( headerName, "Default Type" ) == 0) {
|
||||
int value = atoi( headerValue );
|
||||
if (!mpw_nameForType( (MPResultType)value )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) };
|
||||
return NULL;
|
||||
}
|
||||
defaultType = (MPResultType)value;
|
||||
}
|
||||
if (strcmp( headerName, "Passwords" ) == 0)
|
||||
importRedacted = strcmp( headerValue, "VISIBLE" ) != 0;
|
||||
|
||||
mpw_free_strings( &headerName, &headerValue, NULL );
|
||||
continue;
|
||||
}
|
||||
if (!headerEnded)
|
||||
continue;
|
||||
if (!fullName) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing header: Full Name" };
|
||||
return NULL;
|
||||
}
|
||||
if (positionInLine >= endOfLine)
|
||||
continue;
|
||||
|
||||
if (!user) {
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return NULL;
|
||||
}
|
||||
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master password doesn't match key ID." };
|
||||
return NULL;
|
||||
}
|
||||
if (!(user = mpw_marshal_user( fullName, masterPassword, algorithm ))) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
user->redacted = importRedacted;
|
||||
user->avatar = avatar;
|
||||
user->defaultType = defaultType;
|
||||
}
|
||||
|
||||
// Site
|
||||
char *siteLoginName = NULL, *siteName = NULL, *siteContent = NULL;
|
||||
char *str_lastUsed = NULL, *str_uses = NULL, *str_type = NULL, *str_algorithm = NULL, *str_counter = NULL;
|
||||
switch (format) {
|
||||
case 0: {
|
||||
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||
char *typeAndVersion = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||
if (typeAndVersion) {
|
||||
str_type = mpw_strdup( strtok( typeAndVersion, ":" ) );
|
||||
str_algorithm = mpw_strdup( strtok( NULL, "" ) );
|
||||
mpw_free_string( &typeAndVersion );
|
||||
}
|
||||
str_counter = mpw_strdup( "1" );
|
||||
siteLoginName = NULL;
|
||||
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
|
||||
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||
char *typeAndVersionAndCounter = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||
if (typeAndVersionAndCounter) {
|
||||
str_type = mpw_strdup( strtok( typeAndVersionAndCounter, ":" ) );
|
||||
str_algorithm = mpw_strdup( strtok( NULL, ":" ) );
|
||||
str_counter = mpw_strdup( strtok( NULL, "" ) );
|
||||
mpw_free_string( &typeAndVersionAndCounter );
|
||||
}
|
||||
siteLoginName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
|
||||
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
|
||||
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unexpected import format: %u", format ) };
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) {
|
||||
MPResultType siteType = (MPResultType)atoi( str_type );
|
||||
if (!mpw_nameForType( siteType )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) };
|
||||
return NULL;
|
||||
}
|
||||
long long int value = atoll( str_counter );
|
||||
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) };
|
||||
return NULL;
|
||||
}
|
||||
MPCounterValue siteCounter = (MPCounterValue)value;
|
||||
value = atoll( str_algorithm );
|
||||
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm ) };
|
||||
return NULL;
|
||||
}
|
||||
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
|
||||
time_t siteLastUsed = mpw_mktime( str_lastUsed );
|
||||
if (!siteLastUsed) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MPMarshalledSite *site = mpw_marshal_site(
|
||||
user, siteName, siteType, siteCounter, siteAlgorithm );
|
||||
if (!site) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
site->uses = (unsigned int)atoi( str_uses );
|
||||
site->lastUsed = siteLastUsed;
|
||||
if (!user->redacted) {
|
||||
// Clear Text
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (siteContent && strlen( siteContent ))
|
||||
site->content = mpw_siteState( masterKey, site->name, site->counter,
|
||||
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
|
||||
if (siteLoginName && strlen( siteLoginName ))
|
||||
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
|
||||
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
|
||||
}
|
||||
else {
|
||||
// Redacted
|
||||
if (siteContent && strlen( siteContent ))
|
||||
site->content = mpw_strdup( siteContent );
|
||||
if (siteLoginName && strlen( siteLoginName ))
|
||||
site->loginContent = mpw_strdup( siteLoginName );
|
||||
}
|
||||
}
|
||||
else {
|
||||
error->type = MPMarshalErrorMissing;
|
||||
error->description = mpw_str(
|
||||
"Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
|
||||
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginName, siteName );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mpw_free_strings( &str_lastUsed, &str_uses, &str_type, &str_algorithm, &str_counter, NULL );
|
||||
mpw_free_strings( &siteLoginName, &siteName, &siteContent, NULL );
|
||||
}
|
||||
mpw_free_strings( &fullName, &keyID, NULL );
|
||||
mpw_free( &masterKey, MPMasterKeySize );
|
||||
|
||||
*error = (MPMarshalError){ .type = MPMarshalSuccess };
|
||||
return user;
|
||||
}
|
||||
|
||||
#if MPW_JSON
|
||||
static void mpw_marshal_read_json_info(
|
||||
const char *in, MPMarshalInfo *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 = mpw_strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) );
|
||||
info->keyID = mpw_strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) );
|
||||
|
||||
json_object_put( json_file );
|
||||
}
|
||||
|
||||
static MPMarshalledUser *mpw_marshal_read_json(
|
||||
const char *in, const char *masterPassword, MPMarshalError *error) {
|
||||
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
|
||||
if (!in || !strlen( in )) {
|
||||
error->type = MPMarshalErrorStructure;
|
||||
error->description = mpw_str( "No input data." );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorStructure, mpw_str( "JSON error: %s", json_tokener_error_desc( json_error ) ) };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Parse import data.
|
||||
MPMasterKey masterKey = NULL;
|
||||
MPAlgorithmVersion masterKeyAlgorithm = (MPAlgorithmVersion)-1;
|
||||
MPMarshalledUser *user = NULL;
|
||||
|
||||
// Section: "export"
|
||||
int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 );
|
||||
if (fileFormat < 1) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported format: %u", fileFormat ) };
|
||||
return NULL;
|
||||
}
|
||||
bool fileRedacted = mpw_get_json_boolean( json_file, "export.redacted", true );
|
||||
|
||||
// Section: "user"
|
||||
unsigned int avatar = (unsigned int)mpw_get_json_int( json_file, "user.avatar", 0 );
|
||||
const char *fullName = mpw_get_json_string( json_file, "user.full_name", NULL );
|
||||
const char *str_lastUsed = mpw_get_json_string( json_file, "user.last_used", NULL );
|
||||
const char *keyID = mpw_get_json_string( json_file, "user.key_id", NULL );
|
||||
int64_t value = mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
|
||||
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %u", value ) };
|
||||
return NULL;
|
||||
}
|
||||
MPAlgorithmVersion algorithm = (MPAlgorithmVersion)value;
|
||||
MPResultType defaultType = (MPResultType)mpw_get_json_int( json_file, "user.default_type", MPResultTypeDefault );
|
||||
if (!mpw_nameForType( defaultType )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
|
||||
return NULL;
|
||||
}
|
||||
time_t lastUsed = mpw_mktime( str_lastUsed );
|
||||
if (!lastUsed) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
|
||||
return NULL;
|
||||
}
|
||||
if (!fullName || !strlen( fullName )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing value for full name." };
|
||||
return NULL;
|
||||
}
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return NULL;
|
||||
}
|
||||
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master password doesn't match key ID." };
|
||||
return NULL;
|
||||
}
|
||||
if (!(user = mpw_marshal_user( fullName, masterPassword, algorithm ))) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
|
||||
return NULL;
|
||||
}
|
||||
user->redacted = fileRedacted;
|
||||
user->avatar = avatar;
|
||||
user->defaultType = defaultType;
|
||||
user->lastUsed = lastUsed;
|
||||
|
||||
// Section "sites"
|
||||
json_object_iter json_site;
|
||||
json_object *json_sites = mpw_get_json_section( json_file, "sites" );
|
||||
json_object_object_foreachC( json_sites, json_site ) {
|
||||
const char *siteName = json_site.key;
|
||||
value = mpw_get_json_int( json_site.val, "algorithm", (int32_t)user->algorithm );
|
||||
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm version: %s: %d", siteName, value ) };
|
||||
return NULL;
|
||||
}
|
||||
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
|
||||
MPResultType siteType = (MPResultType)mpw_get_json_int( json_site.val, "type", (int32_t)user->defaultType );
|
||||
if (!mpw_nameForType( siteType )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) };
|
||||
return NULL;
|
||||
}
|
||||
value = mpw_get_json_int( json_site.val, "counter", 1 );
|
||||
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, value ) };
|
||||
return NULL;
|
||||
}
|
||||
MPCounterValue siteCounter = (MPCounterValue)value;
|
||||
const char *siteContent = mpw_get_json_string( json_site.val, "password", NULL );
|
||||
const char *siteLoginName = mpw_get_json_string( json_site.val, "login_name", NULL );
|
||||
MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName );
|
||||
unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 );
|
||||
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
|
||||
time_t siteLastUsed = mpw_mktime( str_lastUsed );
|
||||
if (!siteLastUsed) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json_object *json_site_mpw = mpw_get_json_section( json_site.val, "_ext_mpw" );
|
||||
const char *siteURL = mpw_get_json_string( json_site_mpw, "url", NULL );
|
||||
|
||||
MPMarshalledSite *site = mpw_marshal_site( user, siteName, siteType, siteCounter, siteAlgorithm );
|
||||
if (!site) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
site->loginType = siteLoginType;
|
||||
site->url = siteURL? mpw_strdup( siteURL ): NULL;
|
||||
site->uses = siteUses;
|
||||
site->lastUsed = siteLastUsed;
|
||||
if (!user->redacted) {
|
||||
// Clear Text
|
||||
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
|
||||
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (siteContent && strlen( siteContent ))
|
||||
site->content = mpw_siteState( masterKey, site->name, site->counter,
|
||||
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
|
||||
if (siteLoginName && strlen( siteLoginName ))
|
||||
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
|
||||
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
|
||||
}
|
||||
else {
|
||||
// Redacted
|
||||
if (siteContent && strlen( siteContent ))
|
||||
site->content = mpw_strdup( siteContent );
|
||||
if (siteLoginName && strlen( siteLoginName ))
|
||||
site->loginContent = mpw_strdup( siteLoginName );
|
||||
}
|
||||
|
||||
json_object_iter json_site_question;
|
||||
json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" );
|
||||
json_object_object_foreachC( json_site_questions, json_site_question ) {
|
||||
MPMarshalledQuestion *question = mpw_marshal_question( site, json_site_question.key );
|
||||
const char *answerContent = mpw_get_json_string( json_site_question.val, "answer", NULL );
|
||||
question->type = (MPResultType)mpw_get_json_int( json_site_question.val, "type", MPResultTypeTemplatePhrase );
|
||||
|
||||
if (!user->redacted) {
|
||||
// Clear Text
|
||||
if (answerContent && strlen( answerContent ))
|
||||
question->content = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
|
||||
MPKeyPurposeRecovery, question->keyword, question->type, answerContent, site->algorithm );
|
||||
}
|
||||
else {
|
||||
// Redacted
|
||||
if (answerContent && strlen( answerContent ))
|
||||
question->content = mpw_strdup( answerContent );
|
||||
}
|
||||
}
|
||||
}
|
||||
json_object_put( json_file );
|
||||
|
||||
*error = (MPMarshalError){ .type = MPMarshalSuccess };
|
||||
return user;
|
||||
}
|
||||
#endif
|
||||
|
||||
MPMarshalInfo *mpw_marshal_read_info(
|
||||
const char *in) {
|
||||
|
||||
MPMarshalInfo *info = malloc( sizeof( MPMarshalInfo ) );
|
||||
*info = (MPMarshalInfo){ .format = MPMarshalFormatNone };
|
||||
|
||||
if (in && strlen( in )) {
|
||||
if (in[0] == '#') {
|
||||
*info = (MPMarshalInfo){ .format = MPMarshalFormatFlat };
|
||||
mpw_marshal_read_flat_info( in, info );
|
||||
}
|
||||
else if (in[0] == '{') {
|
||||
*info = (MPMarshalInfo){ .format = MPMarshalFormatJSON };
|
||||
#if MPW_JSON
|
||||
mpw_marshal_read_json_info( in, info );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
MPMarshalledUser *mpw_marshal_read(
|
||||
const char *in, const MPMarshalFormat inFormat, const char *masterPassword, MPMarshalError *error) {
|
||||
|
||||
switch (inFormat) {
|
||||
case MPMarshalFormatNone:
|
||||
*error = (MPMarshalError){ .type = MPMarshalSuccess };
|
||||
return false;
|
||||
case MPMarshalFormatFlat:
|
||||
return mpw_marshal_read_flat( in, masterPassword, error );
|
||||
#if MPW_JSON
|
||||
case MPMarshalFormatJSON:
|
||||
return mpw_marshal_read_json( in, masterPassword, error );
|
||||
#endif
|
||||
default:
|
||||
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) };
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const MPMarshalFormat mpw_formatWithName(
|
||||
const char *formatName) {
|
||||
|
||||
if (!formatName || !strlen( formatName ))
|
||||
return MPMarshalFormatNone;
|
||||
|
||||
// Lower-case to standardize it.
|
||||
size_t stdFormatNameSize = strlen( formatName );
|
||||
char stdFormatName[stdFormatNameSize + 1];
|
||||
for (size_t c = 0; c < stdFormatNameSize; ++c)
|
||||
stdFormatName[c] = (char)tolower( formatName[c] );
|
||||
stdFormatName[stdFormatNameSize] = '\0';
|
||||
|
||||
if (strncmp( mpw_nameForFormat( MPMarshalFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
|
||||
return MPMarshalFormatNone;
|
||||
if (strncmp( mpw_nameForFormat( MPMarshalFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
|
||||
return MPMarshalFormatFlat;
|
||||
if (strncmp( mpw_nameForFormat( MPMarshalFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
|
||||
return MPMarshalFormatJSON;
|
||||
|
||||
dbg( "Not a format name: %s", stdFormatName );
|
||||
return (MPMarshalFormat)ERR;
|
||||
}
|
||||
|
||||
const char *mpw_nameForFormat(
|
||||
const MPMarshalFormat format) {
|
||||
|
||||
switch (format) {
|
||||
case MPMarshalFormatNone:
|
||||
return "none";
|
||||
case MPMarshalFormatFlat:
|
||||
return "flat";
|
||||
case MPMarshalFormatJSON:
|
||||
return "json";
|
||||
default: {
|
||||
dbg( "Unknown format: %d", format );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_marshal_format_extension(
|
||||
const MPMarshalFormat format) {
|
||||
|
||||
switch (format) {
|
||||
case MPMarshalFormatNone:
|
||||
return NULL;
|
||||
case MPMarshalFormatFlat:
|
||||
return "mpsites";
|
||||
case MPMarshalFormatJSON:
|
||||
return "mpsites.json";
|
||||
default: {
|
||||
dbg( "Unknown format: %d", format );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#ifndef _MPW_MARSHAL_H
|
||||
#define _MPW_MARSHAL_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "mpw-algorithm.h"
|
||||
|
||||
//// Types.
|
||||
|
||||
typedef mpw_enum( unsigned int, MPMarshalFormat ) {
|
||||
/** Do not marshal. */
|
||||
MPMarshalFormatNone,
|
||||
/** Marshal using the line-based plain-text format. */
|
||||
MPMarshalFormatFlat,
|
||||
/** Marshal using the JSON structured format. */
|
||||
MPMarshalFormatJSON,
|
||||
|
||||
#if MPW_JSON
|
||||
MPMarshalFormatDefault = MPMarshalFormatJSON,
|
||||
#else
|
||||
MPMarshalFormatDefault = MPMarshalFormatFlat,
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef mpw_enum( unsigned int, MPMarshalErrorType ) {
|
||||
/** The marshalling operation completed successfully. */
|
||||
MPMarshalSuccess,
|
||||
/** An error in the structure of the marshall file interrupted marshalling. */
|
||||
MPMarshalErrorStructure,
|
||||
/** The marshall file uses an unsupported format version. */
|
||||
MPMarshalErrorFormat,
|
||||
/** A required value is missing or not specified. */
|
||||
MPMarshalErrorMissing,
|
||||
/** The given master password is not valid. */
|
||||
MPMarshalErrorMasterPassword,
|
||||
/** An illegal value was specified. */
|
||||
MPMarshalErrorIllegal,
|
||||
/** An internal system error interrupted marshalling. */
|
||||
MPMarshalErrorInternal,
|
||||
};
|
||||
typedef struct MPMarshalError {
|
||||
MPMarshalErrorType type;
|
||||
const char *description;
|
||||
} MPMarshalError;
|
||||
|
||||
typedef struct MPMarshalledQuestion {
|
||||
const char *keyword;
|
||||
const char *content;
|
||||
MPResultType type;
|
||||
} MPMarshalledQuestion;
|
||||
|
||||
typedef struct MPMarshalledSite {
|
||||
const char *name;
|
||||
const char *content;
|
||||
MPResultType type;
|
||||
MPCounterValue counter;
|
||||
MPAlgorithmVersion algorithm;
|
||||
|
||||
const char *loginContent;
|
||||
MPResultType loginType;
|
||||
|
||||
const char *url;
|
||||
unsigned int uses;
|
||||
time_t lastUsed;
|
||||
|
||||
size_t questions_count;
|
||||
MPMarshalledQuestion *questions;
|
||||
} MPMarshalledSite;
|
||||
|
||||
typedef struct MPMarshalledUser {
|
||||
const char *fullName;
|
||||
const char *masterPassword;
|
||||
MPAlgorithmVersion algorithm;
|
||||
bool redacted;
|
||||
|
||||
unsigned int avatar;
|
||||
MPResultType defaultType;
|
||||
time_t lastUsed;
|
||||
|
||||
size_t sites_count;
|
||||
MPMarshalledSite *sites;
|
||||
} MPMarshalledUser;
|
||||
|
||||
typedef struct MPMarshalInfo {
|
||||
MPMarshalFormat format;
|
||||
MPAlgorithmVersion algorithm;
|
||||
const char *fullName;
|
||||
const char *keyID;
|
||||
bool redacted;
|
||||
time_t date;
|
||||
} MPMarshalInfo;
|
||||
|
||||
//// Marshalling.
|
||||
|
||||
/** Write the user and all associated data out to the given output buffer using the given marshalling format. */
|
||||
bool mpw_marshal_write(
|
||||
char **out, const MPMarshalFormat outFormat, const MPMarshalledUser *user, MPMarshalError *error);
|
||||
/** Try to read metadata on the sites in the input buffer. */
|
||||
MPMarshalInfo *mpw_marshal_read_info(
|
||||
const char *in);
|
||||
/** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */
|
||||
MPMarshalledUser *mpw_marshal_read(
|
||||
const char *in, const MPMarshalFormat inFormat, const char *masterPassword, MPMarshalError *error);
|
||||
|
||||
//// Utilities.
|
||||
|
||||
/** Create a new user object ready for marshalling. */
|
||||
MPMarshalledUser *mpw_marshal_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_marshal_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(
|
||||
MPMarshalInfo **info);
|
||||
bool mpw_marshal_free(
|
||||
MPMarshalledUser **user);
|
||||
|
||||
//// Format.
|
||||
|
||||
/**
|
||||
* @return The purpose represented by the given name.
|
||||
*/
|
||||
const MPMarshalFormat mpw_formatWithName(
|
||||
const char *formatName);
|
||||
/**
|
||||
* @return The standard name for the given purpose.
|
||||
*/
|
||||
const char *mpw_nameForFormat(
|
||||
const MPMarshalFormat format);
|
||||
/**
|
||||
* @return The file extension that's recommended for files that use the given marshalling format.
|
||||
*/
|
||||
const char *mpw_marshal_format_extension(
|
||||
const MPMarshalFormat format);
|
||||
|
||||
#endif // _MPW_MARSHAL_H
|
@ -1,267 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
const size_t MPMasterKeySize = 64;
|
||||
const size_t MPSiteKeySize = 256 / 8; // Size of HMAC-SHA-256
|
||||
|
||||
const MPResultType mpw_typeWithName(const char *typeName) {
|
||||
|
||||
// Find what password type is represented by the type letter.
|
||||
if (strlen( typeName ) == 1) {
|
||||
if ('x' == typeName[0])
|
||||
return MPResultTypeTemplateMaximum;
|
||||
if ('l' == typeName[0])
|
||||
return MPResultTypeTemplateLong;
|
||||
if ('m' == typeName[0])
|
||||
return MPResultTypeTemplateMedium;
|
||||
if ('b' == typeName[0])
|
||||
return MPResultTypeTemplateBasic;
|
||||
if ('s' == typeName[0])
|
||||
return MPResultTypeTemplateShort;
|
||||
if ('i' == typeName[0])
|
||||
return MPResultTypeTemplatePIN;
|
||||
if ('n' == typeName[0])
|
||||
return MPResultTypeTemplateName;
|
||||
if ('p' == typeName[0])
|
||||
return MPResultTypeTemplatePhrase;
|
||||
if ('P' == typeName[0])
|
||||
return MPResultTypeStatefulPersonal;
|
||||
if ('D' == typeName[0])
|
||||
return MPResultTypeStatefulDevice;
|
||||
if ('K' == typeName[0])
|
||||
return MPResultTypeDeriveKey;
|
||||
}
|
||||
|
||||
// Lower-case typeName to standardize it.
|
||||
size_t stdTypeNameSize = strlen( typeName );
|
||||
char stdTypeName[stdTypeNameSize + 1];
|
||||
for (size_t c = 0; c < stdTypeNameSize; ++c)
|
||||
stdTypeName[c] = (char)tolower( typeName[c] );
|
||||
stdTypeName[stdTypeNameSize] = '\0';
|
||||
|
||||
// Find what password type is represented by the type name.
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateMaximum ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateMaximum;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateLong ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateLong;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateMedium ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateMedium;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateBasic ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateBasic;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateShort ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateShort;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplatePIN ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplatePIN;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateName ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateName;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplatePhrase;
|
||||
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;
|
||||
|
||||
dbg( "Not a generated type name: %s", stdTypeName );
|
||||
return (MPResultType)ERR;
|
||||
}
|
||||
|
||||
const char *mpw_nameForType(MPResultType resultType) {
|
||||
|
||||
switch (resultType) {
|
||||
case MPResultTypeTemplateMaximum:
|
||||
return "maximum";
|
||||
case MPResultTypeTemplateLong:
|
||||
return "long";
|
||||
case MPResultTypeTemplateMedium:
|
||||
return "medium";
|
||||
case MPResultTypeTemplateBasic:
|
||||
return "basic";
|
||||
case MPResultTypeTemplateShort:
|
||||
return "short";
|
||||
case MPResultTypeTemplatePIN:
|
||||
return "pin";
|
||||
case MPResultTypeTemplateName:
|
||||
return "name";
|
||||
case MPResultTypeTemplatePhrase:
|
||||
return "phrase";
|
||||
case MPResultTypeStatefulPersonal:
|
||||
return "personal";
|
||||
case MPResultTypeStatefulDevice:
|
||||
return "device";
|
||||
case MPResultTypeDeriveKey:
|
||||
return "key";
|
||||
default: {
|
||||
dbg( "Unknown password type: %d", resultType );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char **mpw_templatesForType(MPResultType type, size_t *count) {
|
||||
|
||||
if (!(type & MPResultTypeClassTemplate)) {
|
||||
dbg( "Not a generated type: %d", type );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MPResultTypeTemplateMaximum:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
|
||||
case MPResultTypeTemplateLong:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
|
||||
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
|
||||
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
|
||||
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
|
||||
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
|
||||
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
|
||||
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
|
||||
case MPResultTypeTemplateMedium:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"CvcnoCvc", "CvcCvcno" );
|
||||
case MPResultTypeTemplateBasic:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"aaanaaan", "aannaaan", "aaannaaa" );
|
||||
case MPResultTypeTemplateShort:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"Cvcn" );
|
||||
case MPResultTypeTemplatePIN:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"nnnn" );
|
||||
case MPResultTypeTemplateName:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"cvccvcvcv" );
|
||||
case MPResultTypeTemplatePhrase:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
|
||||
default: {
|
||||
dbg( "Unknown generated type: %d", type );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_templateForType(MPResultType type, uint8_t templateIndex) {
|
||||
|
||||
size_t count = 0;
|
||||
const char **templates = mpw_templatesForType( type, &count );
|
||||
char const *template = templates && count? templates[templateIndex % count]: NULL;
|
||||
free( templates );
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
const MPKeyPurpose mpw_purposeWithName(const char *purposeName) {
|
||||
|
||||
// Lower-case and trim optionally leading "generated" string from typeName to standardize it.
|
||||
size_t stdPurposeNameSize = strlen( purposeName );
|
||||
char stdPurposeName[stdPurposeNameSize + 1];
|
||||
for (size_t c = 0; c < stdPurposeNameSize; ++c)
|
||||
stdPurposeName[c] = (char)tolower( purposeName[c] );
|
||||
stdPurposeName[stdPurposeNameSize] = '\0';
|
||||
|
||||
if (strncmp( mpw_nameForPurpose( MPKeyPurposeAuthentication ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||
return MPKeyPurposeAuthentication;
|
||||
if (strncmp( mpw_nameForPurpose( MPKeyPurposeIdentification ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||
return MPKeyPurposeIdentification;
|
||||
if (strncmp( mpw_nameForPurpose( MPKeyPurposeRecovery ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||
return MPKeyPurposeRecovery;
|
||||
|
||||
dbg( "Not a purpose name: %s", stdPurposeName );
|
||||
return (MPKeyPurpose)ERR;
|
||||
}
|
||||
|
||||
const char *mpw_nameForPurpose(MPKeyPurpose purpose) {
|
||||
|
||||
switch (purpose) {
|
||||
case MPKeyPurposeAuthentication:
|
||||
return "authentication";
|
||||
case MPKeyPurposeIdentification:
|
||||
return "identification";
|
||||
case MPKeyPurposeRecovery:
|
||||
return "recovery";
|
||||
default: {
|
||||
dbg( "Unknown purpose: %d", purpose );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_scopeForPurpose(MPKeyPurpose purpose) {
|
||||
|
||||
switch (purpose) {
|
||||
case MPKeyPurposeAuthentication:
|
||||
return "com.lyndir.masterpassword";
|
||||
case MPKeyPurposeIdentification:
|
||||
return "com.lyndir.masterpassword.login";
|
||||
case MPKeyPurposeRecovery:
|
||||
return "com.lyndir.masterpassword.answer";
|
||||
default: {
|
||||
dbg( "Unknown purpose: %d", purpose );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_charactersInClass(char characterClass) {
|
||||
|
||||
switch (characterClass) {
|
||||
case 'V':
|
||||
return "AEIOU";
|
||||
case 'C':
|
||||
return "BCDFGHJKLMNPQRSTVWXYZ";
|
||||
case 'v':
|
||||
return "aeiou";
|
||||
case 'c':
|
||||
return "bcdfghjklmnpqrstvwxyz";
|
||||
case 'A':
|
||||
return "AEIOUBCDFGHJKLMNPQRSTVWXYZ";
|
||||
case 'a':
|
||||
return "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";
|
||||
case 'n':
|
||||
return "0123456789";
|
||||
case 'o':
|
||||
return "@&%?,=[]_:-+*$#!'^~;()/.";
|
||||
case 'x':
|
||||
return "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
|
||||
case ' ':
|
||||
return " ";
|
||||
default: {
|
||||
dbg( "Unknown character class: %c", characterClass );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
|
||||
|
||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||
if (!classCharacters)
|
||||
return '\0';
|
||||
|
||||
return classCharacters[seedByte % strlen( classCharacters )];
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#ifndef _MPW_TYPES_H
|
||||
#define _MPW_TYPES_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef NS_ENUM
|
||||
#define mpw_enum(_type, _name) NS_ENUM(_type, _name)
|
||||
#else
|
||||
#define mpw_enum(_type, _name) _type _name; enum
|
||||
#endif
|
||||
|
||||
#ifndef __unused
|
||||
#define __unused __attribute__((unused))
|
||||
#endif
|
||||
|
||||
//// Types.
|
||||
|
||||
extern const size_t MPMasterKeySize, MPSiteKeySize; /* bytes */
|
||||
typedef const uint8_t *MPMasterKey, *MPSiteKey;
|
||||
typedef const char *MPKeyID;
|
||||
|
||||
typedef mpw_enum( uint8_t, MPKeyPurpose ) {
|
||||
/** Generate a key for authentication. */
|
||||
MPKeyPurposeAuthentication,
|
||||
/** Generate a name for identification. */
|
||||
MPKeyPurposeIdentification,
|
||||
/** Generate a recovery token. */
|
||||
MPKeyPurposeRecovery,
|
||||
};
|
||||
|
||||
// bit 4 - 9
|
||||
typedef mpw_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. */
|
||||
MPResultTypeClassStateful = 1 << 5,
|
||||
/** Use the site key to derive a site-specific object. */
|
||||
MPResultTypeClassDerive = 1 << 6,
|
||||
};
|
||||
|
||||
// bit 10 - 15
|
||||
typedef mpw_enum( uint16_t, MPSiteFeature ) {
|
||||
/** Export the key-protected content data. */
|
||||
MPSiteFeatureExportContent = 1 << 10,
|
||||
/** Never export content. */
|
||||
MPSiteFeatureDevicePrivate = 1 << 11,
|
||||
/** Don't use this as the primary authentication result type. */
|
||||
MPSiteFeatureAlternative = 1 << 12,
|
||||
};
|
||||
|
||||
// bit 0-3 | MPResultTypeClass | MPSiteFeature
|
||||
typedef mpw_enum( uint32_t, MPResultType ) {
|
||||
/** 16: pg^VMAUBk5x3p%HP%i4= */
|
||||
MPResultTypeTemplateMaximum = 0x0 | MPResultTypeClassTemplate | 0x0,
|
||||
/** 17: BiroYena8:Kixa */
|
||||
MPResultTypeTemplateLong = 0x1 | MPResultTypeClassTemplate | 0x0,
|
||||
/** 18: BirSuj0- */
|
||||
MPResultTypeTemplateMedium = 0x2 | MPResultTypeClassTemplate | 0x0,
|
||||
/** 19: Bir8 */
|
||||
MPResultTypeTemplateShort = 0x3 | MPResultTypeClassTemplate | 0x0,
|
||||
/** 20: pO98MoD0 */
|
||||
MPResultTypeTemplateBasic = 0x4 | MPResultTypeClassTemplate | 0x0,
|
||||
/** 21: 2798 */
|
||||
MPResultTypeTemplatePIN = 0x5 | MPResultTypeClassTemplate | 0x0,
|
||||
/** 30: birsujano */
|
||||
MPResultTypeTemplateName = 0xE | MPResultTypeClassTemplate | 0x0,
|
||||
/** 31: bir yennoquce fefi */
|
||||
MPResultTypeTemplatePhrase = 0xF | MPResultTypeClassTemplate | 0x0,
|
||||
|
||||
/** 1056: Custom saved password. */
|
||||
MPResultTypeStatefulPersonal = 0x0 | MPResultTypeClassStateful | MPSiteFeatureExportContent,
|
||||
/** 2081: Custom saved password that should not be exported from the device. */
|
||||
MPResultTypeStatefulDevice = 0x1 | MPResultTypeClassStateful | MPSiteFeatureDevicePrivate,
|
||||
|
||||
/** 4160: Derive a unique binary key. */
|
||||
MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative,
|
||||
|
||||
MPResultTypeDefault = MPResultTypeTemplateLong,
|
||||
};
|
||||
|
||||
typedef mpw_enum ( uint32_t, MPCounterValue ) {
|
||||
/** Use a time-based counter value, resulting in a TOTP generator. */
|
||||
MPCounterValueTOTP = 0,
|
||||
/** The initial value for a site's counter. */
|
||||
MPCounterValueInitial = 1,
|
||||
|
||||
MPCounterValueDefault = MPCounterValueInitial,
|
||||
MPCounterValueFirst = MPCounterValueTOTP,
|
||||
MPCounterValueLast = UINT32_MAX,
|
||||
};
|
||||
|
||||
/** These colours are compatible with the original ANSI SGR. */
|
||||
typedef mpw_enum( uint8_t, MPIdenticonColor ) {
|
||||
MPIdenticonColorRed = 1,
|
||||
MPIdenticonColorGreen,
|
||||
MPIdenticonColorYellow,
|
||||
MPIdenticonColorBlue,
|
||||
MPIdenticonColorMagenta,
|
||||
MPIdenticonColorCyan,
|
||||
MPIdenticonColorWhite,
|
||||
|
||||
MPIdenticonColorFirst = MPIdenticonColorRed,
|
||||
MPIdenticonColorLast = MPIdenticonColorWhite,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *leftArm;
|
||||
const char *body;
|
||||
const char *rightArm;
|
||||
const char *accessory;
|
||||
MPIdenticonColor color;
|
||||
} MPIdenticon;
|
||||
|
||||
//// Type utilities.
|
||||
|
||||
/**
|
||||
* @return The purpose represented by the given name.
|
||||
*/
|
||||
const MPKeyPurpose mpw_purposeWithName(const char *purposeName);
|
||||
/**
|
||||
* @return The standard name for the given purpose.
|
||||
*/
|
||||
const char *mpw_nameForPurpose(MPKeyPurpose purpose);
|
||||
/**
|
||||
* @return An internal string containing the scope identifier to apply when encoding for the given purpose.
|
||||
*/
|
||||
const char *mpw_scopeForPurpose(MPKeyPurpose purpose);
|
||||
|
||||
/**
|
||||
* @return The password type represented by the given name.
|
||||
*/
|
||||
const MPResultType mpw_typeWithName(const char *typeName);
|
||||
/**
|
||||
* @return The standard name for the given password type.
|
||||
*/
|
||||
const char *mpw_nameForType(MPResultType resultType);
|
||||
|
||||
/**
|
||||
* @return A newly allocated array of internal strings that express the templates to use for the given type.
|
||||
* The amount of elements in the array is stored in count.
|
||||
* If an unsupported type is given, count will be 0 and will return NULL.
|
||||
* The array needs to be free'ed, the strings themselves must not be free'ed or modified.
|
||||
*/
|
||||
const char **mpw_templatesForType(MPResultType type, size_t *count);
|
||||
/**
|
||||
* @return An internal string that contains the password encoding template of the given type
|
||||
* for a seed that starts with the given byte.
|
||||
*/
|
||||
const char *mpw_templateForType(MPResultType type, uint8_t templateIndex);
|
||||
|
||||
/**
|
||||
* @return An internal string that contains all the characters that occur in the given character class.
|
||||
*/
|
||||
const char *mpw_charactersInClass(char characterClass);
|
||||
/**
|
||||
* @return A character from given character class that encodes the given byte.
|
||||
*/
|
||||
const char mpw_characterFromClass(char characterClass, uint8_t seedByte);
|
||||
|
||||
#endif // _MPW_TYPES_H
|
@ -1,498 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if MPW_CPERCIVA
|
||||
#include <scrypt/crypto_scrypt.h>
|
||||
#include <scrypt/sha256.h>
|
||||
#elif MPW_SODIUM
|
||||
#include "sodium.h"
|
||||
#endif
|
||||
#define AES_ECB 0
|
||||
#define AES_CBC 1
|
||||
#include "aes.h"
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
||||
#ifdef inf_level
|
||||
int mpw_verbosity = inf_level;
|
||||
#endif
|
||||
|
||||
void mpw_uint16(const uint16_t number, uint8_t buf[2]) {
|
||||
|
||||
buf[0] = (uint8_t)((number >> 8L) & UINT8_MAX);
|
||||
buf[1] = (uint8_t)((number >> 0L) & UINT8_MAX);
|
||||
}
|
||||
|
||||
void mpw_uint32(const uint32_t number, uint8_t buf[4]) {
|
||||
|
||||
buf[0] = (uint8_t)((number >> 24) & UINT8_MAX);
|
||||
buf[1] = (uint8_t)((number >> 16) & UINT8_MAX);
|
||||
buf[2] = (uint8_t)((number >> 8L) & UINT8_MAX);
|
||||
buf[3] = (uint8_t)((number >> 0L) & UINT8_MAX);
|
||||
}
|
||||
|
||||
void mpw_uint64(const uint64_t number, uint8_t buf[8]) {
|
||||
|
||||
buf[0] = (uint8_t)((number >> 56) & UINT8_MAX);
|
||||
buf[1] = (uint8_t)((number >> 48) & UINT8_MAX);
|
||||
buf[2] = (uint8_t)((number >> 40) & UINT8_MAX);
|
||||
buf[3] = (uint8_t)((number >> 32) & UINT8_MAX);
|
||||
buf[4] = (uint8_t)((number >> 24) & UINT8_MAX);
|
||||
buf[5] = (uint8_t)((number >> 16) & UINT8_MAX);
|
||||
buf[6] = (uint8_t)((number >> 8L) & UINT8_MAX);
|
||||
buf[7] = (uint8_t)((number >> 0L) & UINT8_MAX);
|
||||
}
|
||||
|
||||
bool mpw_push_buf(uint8_t **buffer, size_t *bufferSize, const void *pushBuffer, const size_t pushSize) {
|
||||
|
||||
if (!buffer || !bufferSize || !pushBuffer || !pushSize)
|
||||
return false;
|
||||
if (*bufferSize == (size_t)ERR)
|
||||
// The buffer was marked as broken, it is missing a previous push. Abort to avoid corrupt content.
|
||||
return false;
|
||||
|
||||
if (!mpw_realloc( buffer, bufferSize, pushSize )) {
|
||||
// realloc failed, we can't push. Mark the buffer as broken.
|
||||
mpw_free( buffer, *bufferSize );
|
||||
*bufferSize = (size_t)ERR;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *bufferOffset = *buffer + *bufferSize - pushSize;
|
||||
memcpy( bufferOffset, pushBuffer, pushSize );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mpw_push_string(uint8_t **buffer, size_t *bufferSize, const char *pushString) {
|
||||
|
||||
return pushString && mpw_push_buf( buffer, bufferSize, pushString, strlen( pushString ) );
|
||||
}
|
||||
|
||||
bool mpw_string_push(char **string, const char *pushString) {
|
||||
|
||||
if (!string || !pushString)
|
||||
return false;
|
||||
if (!*string)
|
||||
*string = calloc( 1, sizeof( char ) );
|
||||
|
||||
size_t stringLength = strlen( *string );
|
||||
return pushString && mpw_push_buf( (uint8_t **const)string, &stringLength, pushString, strlen( pushString ) + 1 );
|
||||
}
|
||||
|
||||
bool mpw_string_pushf(char **string, const char *pushFormat, ...) {
|
||||
|
||||
va_list args;
|
||||
va_start( args, pushFormat );
|
||||
bool success = mpw_string_push( string, mpw_vstr( pushFormat, args ) );
|
||||
va_end( args );
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mpw_push_int(uint8_t **buffer, size_t *bufferSize, const uint32_t pushInt) {
|
||||
|
||||
uint8_t pushBuf[4 /* 32 / 8 */];
|
||||
mpw_uint32( pushInt, pushBuf );
|
||||
return mpw_push_buf( buffer, bufferSize, &pushBuf, sizeof( pushBuf ) );
|
||||
}
|
||||
|
||||
bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize) {
|
||||
|
||||
if (!buffer)
|
||||
return false;
|
||||
|
||||
void *newBuffer = realloc( (void *)*buffer, (bufferSize? *bufferSize: 0) + deltaSize );
|
||||
if (!newBuffer)
|
||||
return false;
|
||||
|
||||
*buffer = newBuffer;
|
||||
if (bufferSize)
|
||||
*bufferSize += deltaSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mpw_zero(void *buffer, size_t bufferSize) {
|
||||
|
||||
uint8_t *b = buffer;
|
||||
for (; bufferSize > 0; --bufferSize)
|
||||
*b++ = 0;
|
||||
}
|
||||
|
||||
bool __mpw_free(void **buffer, const size_t bufferSize) {
|
||||
|
||||
if (!buffer || !*buffer)
|
||||
return false;
|
||||
|
||||
mpw_zero( *buffer, bufferSize );
|
||||
free( *buffer );
|
||||
*buffer = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __mpw_free_string(char **string) {
|
||||
|
||||
return *string && __mpw_free( (void **)string, strlen( *string ) );
|
||||
}
|
||||
|
||||
bool __mpw_free_strings(char **strings, ...) {
|
||||
|
||||
bool success = true;
|
||||
|
||||
va_list args;
|
||||
va_start( args, strings );
|
||||
success &= mpw_free_string( strings );
|
||||
for (char **string; (string = va_arg( args, char ** ));)
|
||||
success &= mpw_free_string( string );
|
||||
va_end( args );
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
uint8_t const *mpw_kdf_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
||||
uint64_t N, uint32_t r, uint32_t p) {
|
||||
|
||||
if (!secret || !salt)
|
||||
return NULL;
|
||||
|
||||
uint8_t *key = malloc( keySize );
|
||||
if (!key)
|
||||
return NULL;
|
||||
|
||||
#if MPW_CPERCIVA
|
||||
if (crypto_scrypt( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) < 0) {
|
||||
mpw_free( &key, keySize );
|
||||
return NULL;
|
||||
}
|
||||
#elif MPW_SODIUM
|
||||
if (crypto_pwhash_scryptsalsa208sha256_ll( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) != 0) {
|
||||
mpw_free( &key, keySize );
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
#error No crypto support for mpw_scrypt.
|
||||
#endif
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
uint8_t const *mpw_kdf_blake2b(const size_t subkeySize, const uint8_t *key, const size_t keySize,
|
||||
const uint8_t *context, const size_t contextSize, const uint64_t id, const char *personal) {
|
||||
|
||||
if (!key || !keySize || !subkeySize) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *subkey = malloc( subkeySize );
|
||||
if (!subkey)
|
||||
return NULL;
|
||||
|
||||
#if MPW_SODIUM
|
||||
if (keySize < crypto_generichash_blake2b_KEYBYTES_MIN || keySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
|
||||
subkeySize < crypto_generichash_blake2b_KEYBYTES_MIN || subkeySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
|
||||
(personal && strlen( personal ) > crypto_generichash_blake2b_PERSONALBYTES)) {
|
||||
errno = EINVAL;
|
||||
free( subkey );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t saltBuf[crypto_generichash_blake2b_SALTBYTES];
|
||||
mpw_zero( saltBuf, sizeof saltBuf );
|
||||
if (id)
|
||||
mpw_uint64( id, saltBuf );
|
||||
|
||||
uint8_t personalBuf[crypto_generichash_blake2b_PERSONALBYTES];
|
||||
mpw_zero( personalBuf, sizeof personalBuf );
|
||||
if (personal && strlen( personal ))
|
||||
memcpy( personalBuf, personal, strlen( personal ) );
|
||||
|
||||
if (crypto_generichash_blake2b_salt_personal( subkey, subkeySize, context, contextSize, key, keySize, saltBuf, personalBuf ) != 0) {
|
||||
mpw_free( &subkey, subkeySize );
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
#error No crypto support for mpw_kdf_blake2b.
|
||||
#endif
|
||||
|
||||
return subkey;
|
||||
}
|
||||
|
||||
uint8_t const *mpw_hash_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *message, const size_t messageSize) {
|
||||
|
||||
if (!key || !keySize || !message || !messageSize)
|
||||
return NULL;
|
||||
|
||||
#if MPW_CPERCIVA
|
||||
uint8_t *const mac = malloc( 32 );
|
||||
if (!mac)
|
||||
return NULL;
|
||||
|
||||
HMAC_SHA256_Buf( key, keySize, message, messageSize, mac );
|
||||
#elif MPW_SODIUM
|
||||
uint8_t *const mac = malloc( crypto_auth_hmacsha256_BYTES );
|
||||
if (!mac)
|
||||
return NULL;
|
||||
|
||||
crypto_auth_hmacsha256_state state;
|
||||
if (crypto_auth_hmacsha256_init( &state, key, keySize ) != 0 ||
|
||||
crypto_auth_hmacsha256_update( &state, message, messageSize ) != 0 ||
|
||||
crypto_auth_hmacsha256_final( &state, mac ) != 0) {
|
||||
mpw_free( &mac, crypto_auth_hmacsha256_BYTES );
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
#error No crypto support for mpw_hmac_sha256.
|
||||
#endif
|
||||
|
||||
return mac;
|
||||
}
|
||||
|
||||
// We do our best to not fail on odd buf's, eg. non-padded cipher texts.
|
||||
static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t keySize, const uint8_t *buf, size_t *bufSize) {
|
||||
|
||||
if (!key || keySize < 16 || !*bufSize)
|
||||
return NULL;
|
||||
|
||||
// IV = zero
|
||||
uint8_t iv[16];
|
||||
mpw_zero( iv, sizeof iv );
|
||||
|
||||
// Add PKCS#7 padding
|
||||
uint32_t aesSize = ((uint32_t)*bufSize + 15 / 16) * 16; // round up to block size.
|
||||
if (encrypt && !(*bufSize % 16)) // add pad block if plain text fits block size.
|
||||
encrypt += 16;
|
||||
uint8_t aesBuf[aesSize];
|
||||
memcpy( aesBuf, buf, *bufSize );
|
||||
memset( aesBuf + *bufSize, aesSize - *bufSize, aesSize - *bufSize );
|
||||
uint8_t *resultBuf = malloc( aesSize );
|
||||
|
||||
if (encrypt)
|
||||
AES_CBC_encrypt_buffer( resultBuf, aesBuf, aesSize, key, iv );
|
||||
else
|
||||
AES_CBC_decrypt_buffer( resultBuf, aesBuf, aesSize, key, iv );
|
||||
mpw_zero( aesBuf, aesSize );
|
||||
mpw_zero( iv, 16 );
|
||||
|
||||
// Truncate PKCS#7 padding
|
||||
if (encrypt)
|
||||
*bufSize = aesSize;
|
||||
else if (*bufSize % 16 == 0 && resultBuf[aesSize - 1] < 16)
|
||||
*bufSize -= resultBuf[aesSize - 1];
|
||||
|
||||
return resultBuf;
|
||||
}
|
||||
|
||||
uint8_t const *mpw_aes_encrypt(const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, size_t *bufSize) {
|
||||
|
||||
return mpw_aes( true, key, keySize, plainBuf, bufSize );
|
||||
}
|
||||
|
||||
uint8_t const *mpw_aes_decrypt(const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, size_t *bufSize) {
|
||||
|
||||
return mpw_aes( false, key, keySize, cipherBuf, bufSize );
|
||||
}
|
||||
|
||||
#if UNUSED
|
||||
const char *mpw_hotp(const uint8_t *key, size_t keySize, uint64_t movingFactor, uint8_t digits, uint8_t truncationOffset) {
|
||||
|
||||
// Hash the moving factor with the key.
|
||||
uint8_t counter[8];
|
||||
mpw_uint64( movingFactor, counter );
|
||||
uint8_t hash[20];
|
||||
hmac_sha1( key, keySize, counter, sizeof( counter ), hash );
|
||||
|
||||
// Determine the offset to select OTP bytes from.
|
||||
int offset;
|
||||
if ((truncationOffset >= 0) && (truncationOffset < (sizeof( hash ) - 4)))
|
||||
offset = truncationOffset;
|
||||
else
|
||||
offset = hash[sizeof( hash ) - 1] & 0xf;
|
||||
|
||||
// Select four bytes from the truncation offset.
|
||||
uint32_t otp = 0U
|
||||
| ((hash[offset + 0] & 0x7f) << 24)
|
||||
| ((hash[offset + 1] & 0xff) << 16)
|
||||
| ((hash[offset + 2] & 0xff) << 8)
|
||||
| ((hash[offset + 3] & 0xff) << 0);
|
||||
|
||||
// Render the OTP as `digits` decimal digits.
|
||||
otp %= (int)pow(10, digits);
|
||||
return mpw_strdup( mpw_str( "%0*d", digits, otp ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
MPKeyID mpw_id_buf(const void *buf, size_t length) {
|
||||
|
||||
if (!buf)
|
||||
return "<unset>";
|
||||
|
||||
#if MPW_CPERCIVA
|
||||
uint8_t hash[32];
|
||||
SHA256_Buf( buf, length, hash );
|
||||
#elif MPW_SODIUM
|
||||
uint8_t hash[crypto_hash_sha256_BYTES];
|
||||
crypto_hash_sha256( hash, buf, length );
|
||||
#else
|
||||
#error No crypto support for mpw_id_buf.
|
||||
#endif
|
||||
|
||||
return mpw_hex( hash, sizeof( hash ) / sizeof( uint8_t ) );
|
||||
}
|
||||
|
||||
bool mpw_id_buf_equals(const char *id1, const char *id2) {
|
||||
|
||||
size_t size = strlen( id1 );
|
||||
if (size != strlen( id2 ))
|
||||
return false;
|
||||
|
||||
for (size_t c = 0; c < size; ++c)
|
||||
if (tolower( id1[c] ) != tolower( id2[c] ))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *mpw_str(const char *format, ...) {
|
||||
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
const char *str_str = mpw_vstr( format, args );
|
||||
va_end( args );
|
||||
|
||||
return str_str;
|
||||
}
|
||||
|
||||
const char *mpw_vstr(const char *format, va_list args) {
|
||||
|
||||
// TODO: We should find a way to get rid of this shared storage medium.
|
||||
// TODO: Not thread-safe
|
||||
static char *str_str;
|
||||
static size_t str_str_max;
|
||||
if (!str_str && !(str_str = calloc( str_str_max = 1, sizeof( char ) )))
|
||||
return NULL;
|
||||
|
||||
do {
|
||||
va_list args_attempt;
|
||||
va_copy( args_attempt, args );
|
||||
size_t len = (size_t)vsnprintf( str_str, str_str_max, format, args_attempt );
|
||||
va_end( args_attempt );
|
||||
|
||||
if ((int)len < 0)
|
||||
return NULL;
|
||||
if (len < str_str_max)
|
||||
break;
|
||||
|
||||
if (!mpw_realloc( &str_str, &str_str_max, len - str_str_max + 1 ))
|
||||
return NULL;
|
||||
} while (true);
|
||||
|
||||
return str_str;
|
||||
}
|
||||
|
||||
const char *mpw_hex(const void *buf, size_t length) {
|
||||
|
||||
// TODO: We should find a way to get rid of this shared storage medium.
|
||||
// TODO: Not thread-safe
|
||||
static char **mpw_hex_buf;
|
||||
static unsigned int mpw_hex_buf_i;
|
||||
|
||||
if (!mpw_hex_buf)
|
||||
mpw_hex_buf = calloc( 10, sizeof( char * ) );
|
||||
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
|
||||
|
||||
if (mpw_realloc( &mpw_hex_buf[mpw_hex_buf_i], NULL, length * 2 + 1 ))
|
||||
for (size_t kH = 0; kH < length; kH++)
|
||||
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
|
||||
|
||||
return mpw_hex_buf[mpw_hex_buf_i];
|
||||
}
|
||||
|
||||
const char *mpw_hex_l(uint32_t number) {
|
||||
|
||||
uint8_t buf[4 /* 32 / 8 */];
|
||||
buf[0] = (uint8_t)((number >> 24) & UINT8_MAX);
|
||||
buf[1] = (uint8_t)((number >> 16) & UINT8_MAX);
|
||||
buf[2] = (uint8_t)((number >> 8L) & UINT8_MAX);
|
||||
buf[3] = (uint8_t)((number >> 0L) & UINT8_MAX);
|
||||
return mpw_hex( &buf, sizeof( buf ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the amount of bytes used by UTF-8 to encode a single character that starts with the given byte.
|
||||
*/
|
||||
static int mpw_utf8_sizeof(unsigned char utf8Byte) {
|
||||
|
||||
if (!utf8Byte)
|
||||
return 0;
|
||||
if ((utf8Byte & 0x80) == 0)
|
||||
return 1;
|
||||
if ((utf8Byte & 0xC0) != 0xC0)
|
||||
return 0;
|
||||
if ((utf8Byte & 0xE0) == 0xC0)
|
||||
return 2;
|
||||
if ((utf8Byte & 0xF0) == 0xE0)
|
||||
return 3;
|
||||
if ((utf8Byte & 0xF8) == 0xF0)
|
||||
return 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t mpw_utf8_strlen(const char *utf8String) {
|
||||
|
||||
size_t charlen = 0;
|
||||
char *remainingString = (char *)utf8String;
|
||||
for (int charByteSize; (charByteSize = mpw_utf8_sizeof( (unsigned char)*remainingString )); remainingString += charByteSize)
|
||||
++charlen;
|
||||
|
||||
return charlen;
|
||||
}
|
||||
|
||||
char *mpw_strdup(const char *src) {
|
||||
|
||||
if (!src)
|
||||
return NULL;
|
||||
|
||||
size_t len = strlen( src );
|
||||
char *dst = malloc( len + 1 );
|
||||
memcpy( dst, src, len );
|
||||
dst[len] = '\0';
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
char *mpw_strndup(const char *src, size_t max) {
|
||||
|
||||
if (!src)
|
||||
return NULL;
|
||||
|
||||
size_t len = 0;
|
||||
for (; len < max && src[len] != '\0'; ++len);
|
||||
|
||||
char *dst = malloc( len + 1 );
|
||||
memcpy( dst, src, len );
|
||||
dst[len] = '\0';
|
||||
|
||||
return dst;
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#ifndef _MPW_UTIL_H
|
||||
#define _MPW_UTIL_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
|
||||
//// Logging.
|
||||
extern int mpw_verbosity;
|
||||
|
||||
#ifndef mpw_log_do
|
||||
#define mpw_log_do(level, format, ...) \
|
||||
fprintf( stderr, format "\n", ##__VA_ARGS__ )
|
||||
#endif
|
||||
|
||||
#ifndef mpw_log
|
||||
#define mpw_log(level, ...) ({ \
|
||||
if (mpw_verbosity >= level) { \
|
||||
mpw_log_do( level, ##__VA_ARGS__ ); \
|
||||
}; })
|
||||
#endif
|
||||
|
||||
#ifndef trc
|
||||
/** Logging internal state. */
|
||||
#define trc_level 3
|
||||
#define trc(...) mpw_log( trc_level, ##__VA_ARGS__ )
|
||||
|
||||
/** Logging state and events interesting when investigating issues. */
|
||||
#define dbg_level 2
|
||||
#define dbg(...) mpw_log( dbg_level, ##__VA_ARGS__ )
|
||||
|
||||
/** User messages. */
|
||||
#define inf_level 1
|
||||
#define inf(...) mpw_log( inf_level, ##__VA_ARGS__ )
|
||||
|
||||
/** Recoverable issues and user suggestions. */
|
||||
#define wrn_level 0
|
||||
#define wrn(...) mpw_log( wrn_level, ##__VA_ARGS__ )
|
||||
|
||||
/** Unrecoverable issues. */
|
||||
#define err_level -1
|
||||
#define err(...) mpw_log( err_level, ##__VA_ARGS__ )
|
||||
|
||||
/** Issues that lead to abortion. */
|
||||
#define ftl_level -2
|
||||
#define ftl(...) mpw_log( ftl_level, ##__VA_ARGS__ )
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ({ \
|
||||
__typeof__ (a) _a = (a); \
|
||||
__typeof__ (b) _b = (b); \
|
||||
_a < _b ? _a : _b; })
|
||||
#endif
|
||||
#ifndef max
|
||||
#define max(a, b) ({ \
|
||||
__typeof__ (a) _a = (a); \
|
||||
__typeof__ (b) _b = (b); \
|
||||
_a > _b ? _a : _b; })
|
||||
#endif
|
||||
#ifndef ERR
|
||||
#define ERR -1
|
||||
#endif
|
||||
#ifndef OK
|
||||
#define OK 0
|
||||
#endif
|
||||
#ifndef stringify
|
||||
#define stringify(s) #s
|
||||
#endif
|
||||
#ifndef stringify_def
|
||||
#define stringify_def(s) stringify(s)
|
||||
#endif
|
||||
|
||||
//// Buffers and memory.
|
||||
|
||||
/** Write a number to a byte buffer using mpw's endianness (big/network endian). */
|
||||
void mpw_uint16(const uint16_t number, uint8_t buf[2]);
|
||||
void mpw_uint32(const uint32_t number, uint8_t buf[4]);
|
||||
void mpw_uint64(const uint64_t number, uint8_t buf[8]);
|
||||
|
||||
/** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs. */
|
||||
#define mpw_alloc_array(_count, _type, ...) ({ \
|
||||
_type stackElements[] = { __VA_ARGS__ }; \
|
||||
if (_count) \
|
||||
*_count = sizeof( stackElements ) / sizeof( _type ); \
|
||||
_type *allocElements = malloc( sizeof( stackElements ) ); \
|
||||
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
|
||||
allocElements; \
|
||||
})
|
||||
|
||||
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
|
||||
bool mpw_push_buf(
|
||||
uint8_t **buffer, size_t *bufferSize, const void *pushBuffer, const size_t pushSize);
|
||||
/** Push a string onto a buffer. reallocs the given buffer and appends the given string. */
|
||||
bool mpw_push_string(
|
||||
uint8_t **buffer, size_t *bufferSize, const char *pushString);
|
||||
/** Push a string onto another string. reallocs the target string and appends the source string. */
|
||||
bool mpw_string_push(
|
||||
char **string, const char *pushString);
|
||||
bool mpw_string_pushf(
|
||||
char **string, const char *pushFormat, ...);
|
||||
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
|
||||
bool mpw_push_int(
|
||||
uint8_t **buffer, size_t *bufferSize, const uint32_t pushInt);
|
||||
/** Reallocate the given buffer from the given size by adding the delta size.
|
||||
* On success, the buffer size pointer will be updated to the buffer's new size
|
||||
* and the buffer pointer may be updated to a new memory address.
|
||||
* On failure, the buffer and pointers will remain unaffected.
|
||||
* @param buffer A pointer to the buffer to reallocate.
|
||||
* @param bufferSize A pointer to the buffer's actual size.
|
||||
* @param deltaSize The amount to increase the buffer's size by.
|
||||
* @return true if successful, false if reallocation failed.
|
||||
*/
|
||||
#define mpw_realloc(buffer, bufferSize, deltaSize) \
|
||||
({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_realloc( (const void **)_b, bufferSize, deltaSize ); })
|
||||
bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize);
|
||||
void mpw_zero(
|
||||
void *buffer, size_t bufferSize);
|
||||
/** Free a buffer after zero'ing its contents, then set the reference to NULL. */
|
||||
#define mpw_free(buffer, bufferSize) \
|
||||
({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_free( (void **)_b, bufferSize ); })
|
||||
bool __mpw_free(
|
||||
void **buffer, size_t bufferSize);
|
||||
/** Free a string after zero'ing its contents, then set the reference to NULL. */
|
||||
#define mpw_free_string(string) \
|
||||
({ __typeof__(string) _s = string; const char *__s = *_s; (void)__s; __mpw_free_string( (char **)_s ); })
|
||||
bool __mpw_free_string(
|
||||
char **string);
|
||||
/** Free strings after zero'ing their contents, then set the references to NULL. Terminate the va_list with NULL. */
|
||||
#define mpw_free_strings(strings, ...) \
|
||||
({ __typeof__(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (char **)_s, __VA_ARGS__ ); })
|
||||
bool __mpw_free_strings(
|
||||
char **strings, ...);
|
||||
|
||||
//// Cryptographic functions.
|
||||
|
||||
/** Derive a key from the given secret and salt using the scrypt KDF.
|
||||
* @return A new keySize allocated buffer containing the key. */
|
||||
uint8_t const *mpw_kdf_scrypt(
|
||||
const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
||||
uint64_t N, uint32_t r, uint32_t p);
|
||||
/** Derive a subkey from the given key using the blake2b KDF.
|
||||
* @return A new keySize allocated buffer containing the key. */
|
||||
uint8_t const *mpw_kdf_blake2b(
|
||||
const size_t subkeySize, const uint8_t *key, const size_t keySize,
|
||||
const uint8_t *context, const size_t contextSize, const uint64_t id, const char *personal);
|
||||
/** Calculate the MAC for the given message with the given key using SHA256-HMAC.
|
||||
* @return A new 32-byte allocated buffer containing the MAC. */
|
||||
uint8_t const *mpw_hash_hmac_sha256(
|
||||
const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize);
|
||||
/** Encrypt a plainBuf with the given key using AES-128-CBC.
|
||||
* @return A new bufSize allocated buffer containing the cipherBuf. */
|
||||
uint8_t const *mpw_aes_encrypt(
|
||||
const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, size_t *bufSize);
|
||||
/** Decrypt a cipherBuf with the given key using AES-128-CBC.
|
||||
* @return A new bufSize allocated buffer containing the plainBuf. */
|
||||
uint8_t const *mpw_aes_decrypt(
|
||||
const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, size_t *bufSize);
|
||||
/** Calculate an OTP using RFC-4226.
|
||||
* @return A newly allocated string containing exactly `digits` decimal OTP digits. */
|
||||
#if UNUSED
|
||||
const char *mpw_hotp(
|
||||
const uint8_t *key, size_t keySize, uint64_t movingFactor, uint8_t digits, uint8_t truncationOffset);
|
||||
#endif
|
||||
|
||||
//// Visualizers.
|
||||
|
||||
/** Compose a formatted string.
|
||||
* @return A C-string in a reused buffer, do not free or store it. */
|
||||
const char *mpw_str(const char *format, ...);
|
||||
const char *mpw_vstr(const char *format, va_list args);
|
||||
/** Encode a buffer as a string of hexadecimal characters.
|
||||
* @return A C-string in a reused buffer, do not free or store it. */
|
||||
const char *mpw_hex(const void *buf, size_t length);
|
||||
const char *mpw_hex_l(uint32_t number);
|
||||
/** Encode a fingerprint for a buffer.
|
||||
* @return A C-string in a reused buffer, do not free or store it. */
|
||||
MPKeyID mpw_id_buf(const void *buf, size_t length);
|
||||
/** Compare two fingerprints for equality.
|
||||
* @return true if the buffers represent identical fingerprints. */
|
||||
bool mpw_id_buf_equals(const char *id1, const char *id2);
|
||||
|
||||
//// String utilities.
|
||||
|
||||
/** @return The amount of display characters in the given UTF-8 string. */
|
||||
const size_t mpw_utf8_strlen(const char *utf8String);
|
||||
/** Drop-in for POSIX strdup(3). */
|
||||
char *mpw_strdup(const char *src);
|
||||
/** Drop-in for POSIX strndup(3). */
|
||||
char *mpw_strndup(const char *src, size_t max);
|
||||
|
||||
#endif // _MPW_UTIL_H
|
@ -1,16 +0,0 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
description = 'Master Password Algorithm Implementation'
|
||||
|
||||
dependencies {
|
||||
compile (group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.6-p11') {
|
||||
exclude( module: 'joda-time' )
|
||||
}
|
||||
compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.6-p11'
|
||||
|
||||
compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0'
|
||||
compile group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
||||
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Algorithm Implementation</name>
|
||||
<description>The implementation of the Master Password algorithm</description>
|
||||
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||
<artifactId>opal-system</artifactId>
|
||||
<version>1.6-p11</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||
<artifactId>opal-crypto</artifactId>
|
||||
<version>1.6-p11</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -1,99 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.MessageDigests;
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @see MPMasterKey.Version
|
||||
*/
|
||||
public interface MPAlgorithm {
|
||||
|
||||
/**
|
||||
* mpw: validity for the time-based rolling counter.
|
||||
*/
|
||||
int mpw_otp_window = 5 * 60 /* s */;
|
||||
|
||||
/**
|
||||
* mpw: Key ID hash.
|
||||
*/
|
||||
MessageDigests mpw_hash = MessageDigests.SHA256;
|
||||
|
||||
/**
|
||||
* mpw: Site digest.
|
||||
*/
|
||||
MessageAuthenticationDigests mpw_digest = MessageAuthenticationDigests.HmacSHA256;
|
||||
|
||||
/**
|
||||
* mpw: Platform-agnostic byte order.
|
||||
*/
|
||||
ByteOrder mpw_byteOrder = ByteOrder.BIG_ENDIAN;
|
||||
|
||||
/**
|
||||
* mpw: Input character encoding.
|
||||
*/
|
||||
Charset mpw_charset = Charsets.UTF_8;
|
||||
|
||||
/**
|
||||
* mpw: Master key size (byte).
|
||||
*/
|
||||
int mpw_dkLen = 64;
|
||||
|
||||
/**
|
||||
* scrypt: Parallelization parameter.
|
||||
*/
|
||||
int scrypt_p = 2;
|
||||
|
||||
/**
|
||||
* scrypt: Memory cost parameter.
|
||||
*/
|
||||
int scrypt_r = 8;
|
||||
|
||||
/**
|
||||
* scrypt: CPU cost parameter.
|
||||
*/
|
||||
int scrypt_N = 32768;
|
||||
|
||||
MPMasterKey.Version getAlgorithmVersion();
|
||||
|
||||
byte[] masterKey(String fullName, char[] masterPassword);
|
||||
|
||||
byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
|
||||
@Nullable String keyContext);
|
||||
|
||||
String siteResult(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
|
||||
@Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
String sitePasswordFromTemplate(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
String sitePasswordFromCrypt(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
String sitePasswordFromDerive(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
String siteState(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
|
||||
@Nullable String keyContext, MPResultType resultType, String resultParam);
|
||||
}
|
@ -1,248 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.masterpassword.MPUtils.*;
|
||||
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lambdaworks.crypto.SCrypt;
|
||||
import com.lyndir.lhunath.opal.crypto.CryptUtils;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import java.nio.*;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see MPMasterKey.Version#V0
|
||||
*/
|
||||
public class MPAlgorithmV0 implements MPAlgorithm {
|
||||
|
||||
protected final Logger logger = Logger.get( getClass() );
|
||||
|
||||
@Override
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MPMasterKey.Version.V0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] masterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
byte[] fullNameBytes = fullName.getBytes( mpw_charset );
|
||||
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
|
||||
|
||||
String keyScope = MPKeyPurpose.Authentication.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
|
||||
// Calculate the master key salt.
|
||||
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
|
||||
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
|
||||
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( mpw_charset ), fullNameLengthBytes, fullNameBytes );
|
||||
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
||||
|
||||
// Calculate the master key.
|
||||
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
|
||||
scrypt_N, scrypt_r, scrypt_p );
|
||||
byte[] masterPasswordBytes = bytesForChars( masterPassword );
|
||||
byte[] masterKey = scrypt( masterKeySalt, masterPasswordBytes );
|
||||
Arrays.fill( masterKeySalt, (byte) 0 );
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
|
||||
try {
|
||||
//if (isAllowNative())
|
||||
return SCrypt.scrypt( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
|
||||
//else
|
||||
// return SCrypt.scryptJ( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
|
||||
}
|
||||
catch (final GeneralSecurityException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext) {
|
||||
|
||||
String keyScope = keyPurpose.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
|
||||
// OTP counter value.
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (mpw_otp_window * 1000)) * mpw_otp_window );
|
||||
|
||||
// Calculate the site seed.
|
||||
byte[] siteNameBytes = siteName.getBytes( mpw_charset );
|
||||
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
|
||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
||||
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( mpw_charset );
|
||||
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
|
||||
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
|
||||
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
|
||||
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (keyContextBytes != null)
|
||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
|
||||
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
||||
|
||||
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
|
||||
byte[] sitePasswordSeedBytes = mpw_digest.of( masterKey, sitePasswordInfo );
|
||||
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
|
||||
|
||||
return sitePasswordSeedBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) {
|
||||
|
||||
switch (resultType.getTypeClass()) {
|
||||
case Template:
|
||||
return sitePasswordFromTemplate( masterKey, siteKey, resultType, resultParam );
|
||||
case Stateful:
|
||||
return sitePasswordFromCrypt( masterKey, siteKey, resultType, resultParam );
|
||||
case Derive:
|
||||
return sitePasswordFromDerive( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
throw logger.bug( "Unsupported result type class: %s", resultType.getTypeClass() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
|
||||
@Nullable final String resultParam) {
|
||||
|
||||
int[] _siteKey = new int[siteKey.length];
|
||||
for (int i = 0; i < siteKey.length; ++i) {
|
||||
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder );
|
||||
Arrays.fill( buf.array(), (byte) ((siteKey[i] > 0)? 0x00: 0xFF) );
|
||||
buf.position( 2 );
|
||||
buf.put( siteKey[i] ).rewind();
|
||||
_siteKey[i] = buf.getInt() & 0xFFFF;
|
||||
}
|
||||
|
||||
// Determine the template.
|
||||
Preconditions.checkState( _siteKey.length > 0 );
|
||||
int templateIndex = _siteKey[0];
|
||||
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
|
||||
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
|
||||
|
||||
// Encode the password from the seed using the template.
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = _siteKey[i + 1];
|
||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( " - class: %c, index: %5d (0x%2H) => character: %c",
|
||||
characterClass.getIdentifier(), characterIndex, _siteKey[i + 1], passwordCharacter );
|
||||
|
||||
password.append( passwordCharacter );
|
||||
}
|
||||
logger.trc( " => password: %s", password );
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sitePasswordFromCrypt(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
|
||||
@Nullable final String resultParam) {
|
||||
|
||||
Preconditions.checkNotNull( resultParam );
|
||||
Preconditions.checkArgument( !resultParam.isEmpty() );
|
||||
|
||||
try {
|
||||
// Base64-decode
|
||||
byte[] cipherBuf = CryptUtils.decodeBase64( resultParam );
|
||||
logger.trc( "b64 decoded: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
|
||||
|
||||
// Decrypt
|
||||
byte[] plainBuf = CryptUtils.decrypt( cipherBuf, masterKey, true );
|
||||
String plainText = mpw_charset.decode( ByteBuffer.wrap( plainBuf ) ).toString();
|
||||
logger.trc( "decrypted -> plainText: %d bytes = %s = %s", plainBuf.length, plainText, CodeUtils.encodeHex( plainBuf ) );
|
||||
|
||||
return plainText;
|
||||
}
|
||||
catch (final BadPaddingException e) {
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sitePasswordFromDerive(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
|
||||
@Nullable final String resultParam) {
|
||||
|
||||
if (resultType == MPResultType.DeriveKey) {
|
||||
int resultParamInt = ConversionUtils.toIntegerNN( resultParam );
|
||||
if (resultParamInt == 0)
|
||||
resultParamInt = 512;
|
||||
if ((resultParamInt < 128) || (resultParamInt > 512) || ((resultParamInt % 8) != 0))
|
||||
throw logger.bug( "Parameter is not a valid key size (should be 128 - 512): %s", resultParam );
|
||||
int keySize = resultParamInt / 8;
|
||||
logger.trc( "keySize: %d", keySize );
|
||||
|
||||
// Derive key
|
||||
byte[] resultKey = null; // TODO: mpw_kdf_blake2b( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL );
|
||||
if (resultKey == null)
|
||||
throw logger.bug( "Could not derive result key." );
|
||||
|
||||
// Base64-encode
|
||||
String b64Key = Verify.verifyNotNull( CryptUtils.encodeBase64( resultKey ) );
|
||||
logger.trc( "b64 encoded -> key: %s", b64Key );
|
||||
|
||||
return b64Key;
|
||||
} else
|
||||
throw logger.bug( "Unsupported derived password type: %s", resultType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext, final MPResultType resultType, final String resultParam) {
|
||||
|
||||
try {
|
||||
// Encrypt
|
||||
byte[] cipherBuf = CryptUtils.encrypt( resultParam.getBytes( mpw_charset ), masterKey, true );
|
||||
logger.trc( "cipherBuf: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
|
||||
|
||||
// Base64-encode
|
||||
String cipherText = Verify.verifyNotNull( CryptUtils.encodeBase64( cipherBuf ) );
|
||||
logger.trc( "b64 encoded -> cipherText: %s", cipherText );
|
||||
|
||||
return cipherText;
|
||||
}
|
||||
catch (final IllegalBlockSizeException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @see MPMasterKey.Version#V1
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MPAlgorithmV1 extends MPAlgorithmV0 {
|
||||
|
||||
@Override
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MPMasterKey.Version.V1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
|
||||
|
||||
// Determine the template.
|
||||
Preconditions.checkState( siteKey.length > 0 );
|
||||
int templateIndex = siteKey[0] & 0xFF; // Convert to unsigned int.
|
||||
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
|
||||
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
|
||||
|
||||
// Encode the password from the seed using the template.
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = siteKey[i + 1] & 0xFF; // Convert to unsigned int.
|
||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( " - class: %c, index: %3d (0x%2H) => character: %c",
|
||||
characterClass.getIdentifier(), characterIndex, siteKey[i + 1], passwordCharacter );
|
||||
|
||||
password.append( passwordCharacter );
|
||||
}
|
||||
logger.trc( " => password: %s", password );
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.masterpassword.MPUtils.*;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @see MPMasterKey.Version#V2
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MPAlgorithmV2 extends MPAlgorithmV1 {
|
||||
|
||||
@Override
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MPMasterKey.Version.V2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext) {
|
||||
|
||||
String keyScope = keyPurpose.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
|
||||
// OTP counter value.
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPAlgorithm.mpw_otp_window * 1000)) * MPAlgorithm.mpw_otp_window );
|
||||
|
||||
// Calculate the site seed.
|
||||
byte[] siteNameBytes = siteName.getBytes( MPAlgorithm.mpw_charset );
|
||||
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
|
||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
||||
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( MPAlgorithm.mpw_charset );
|
||||
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
|
||||
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
|
||||
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
|
||||
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( MPAlgorithm.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (keyContextBytes != null)
|
||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
|
||||
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
||||
|
||||
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
|
||||
byte[] sitePasswordSeedBytes = MPAlgorithm.mpw_digest.of( masterKey, sitePasswordInfo );
|
||||
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
|
||||
|
||||
return sitePasswordSeedBytes;
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.masterpassword.MPUtils.*;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @see MPMasterKey.Version#V3
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MPAlgorithmV3 extends MPAlgorithmV2 {
|
||||
|
||||
@Override
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MPMasterKey.Version.V3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] masterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
byte[] fullNameBytes = fullName.getBytes( MPAlgorithm.mpw_charset );
|
||||
byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length );
|
||||
|
||||
String keyScope = MPKeyPurpose.Authentication.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
|
||||
// Calculate the master key salt.
|
||||
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
|
||||
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
|
||||
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( MPAlgorithm.mpw_charset ), fullNameLengthBytes, fullNameBytes );
|
||||
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
||||
|
||||
// Calculate the master key.
|
||||
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
|
||||
MPAlgorithm.scrypt_N, MPAlgorithm.scrypt_r, MPAlgorithm.scrypt_p );
|
||||
byte[] mpBytes = bytesForChars( masterPassword );
|
||||
byte[] masterKey = scrypt( masterKeySalt, mpBytes );
|
||||
Arrays.fill( masterKeySalt, (byte) 0 );
|
||||
Arrays.fill( mpBytes, (byte) 0 );
|
||||
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2016-10-29
|
||||
*/
|
||||
public final class MPConstant {
|
||||
|
||||
/* Environment */
|
||||
|
||||
/**
|
||||
* mpw: default path to look for run configuration files if the platform default is not desired.
|
||||
*/
|
||||
public static final String env_rcDir = "MPW_RCDIR";
|
||||
/**
|
||||
* mpw: permit automatic update checks.
|
||||
*/
|
||||
public static final String env_checkUpdates = "MPW_CHECKUPDATES";
|
||||
|
||||
/* Algorithm */
|
||||
|
||||
public static final int MS_PER_S = 1000;
|
||||
|
||||
public static final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis();
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-03-29
|
||||
*/
|
||||
public class MPIdenticon {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPIdenticon.class );
|
||||
|
||||
private static final Charset charset = Charsets.UTF_8;
|
||||
private static final Color[] colors = {
|
||||
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
|
||||
private static final char[] leftArm = { '╔', '╚', '╰', '═' };
|
||||
private static final char[] rightArm = { '╗', '╝', '╯', '═' };
|
||||
private static final char[] body = { '█', '░', '▒', '▓', '☺', '☻' };
|
||||
private static final char[] accessory = {
|
||||
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
|
||||
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
|
||||
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
|
||||
|
||||
private final String fullName;
|
||||
private final Color color;
|
||||
private final String text;
|
||||
|
||||
public MPIdenticon(final String fullName, final String masterPassword) {
|
||||
this( fullName, masterPassword.toCharArray() );
|
||||
}
|
||||
|
||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
||||
public MPIdenticon(final String fullName, final char[] masterPassword) {
|
||||
this.fullName = fullName;
|
||||
|
||||
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
|
||||
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
|
||||
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
|
||||
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
|
||||
while (identiconSeedBytes.hasRemaining())
|
||||
identiconSeedBuffer.put( identiconSeedBytes.get() & 0xFF );
|
||||
int[] identiconSeed = identiconSeedBuffer.array();
|
||||
|
||||
color = colors[identiconSeed[4] % colors.length];
|
||||
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
|
||||
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
RED,
|
||||
GREEN,
|
||||
YELLOW,
|
||||
BLUE,
|
||||
MAGENTA,
|
||||
CYAN,
|
||||
MONO
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-21
|
||||
*/
|
||||
public class MPInvalidatedException extends Exception {
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Locale;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-02
|
||||
*/
|
||||
public enum MPKeyPurpose {
|
||||
/**
|
||||
* Generate a key for authentication.
|
||||
*/
|
||||
Authentication( "authentication", "Generate a key for authentication.", "com.lyndir.masterpassword" ),
|
||||
/**
|
||||
* Generate a name for identification.
|
||||
*/
|
||||
Identification( "identification", "Generate a name for identification.", "com.lyndir.masterpassword.login" ),
|
||||
/**
|
||||
* Generate a recovery token.
|
||||
*/
|
||||
Recovery( "recovery", "Generate a recovery token.", "com.lyndir.masterpassword.answer" );
|
||||
|
||||
static final Logger logger = Logger.get( MPResultType.class );
|
||||
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
private final String scope;
|
||||
|
||||
MPKeyPurpose(final String shortName, final String description, @NonNls final String scope) {
|
||||
this.shortName = shortName;
|
||||
this.description = description;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shortNamePrefix The name for the purpose to look up. It is a case insensitive prefix of the purpose's short name.
|
||||
*
|
||||
* @return The purpose registered with the given name.
|
||||
*/
|
||||
@Nullable
|
||||
@Contract("!null -> !null")
|
||||
public static MPKeyPurpose forName(@Nullable final String shortNamePrefix) {
|
||||
|
||||
if (shortNamePrefix == null)
|
||||
return null;
|
||||
|
||||
for (final MPKeyPurpose type : values())
|
||||
if (type.getShortName().toLowerCase( Locale.ROOT ).startsWith( shortNamePrefix.toLowerCase( Locale.ROOT ) ))
|
||||
return type;
|
||||
|
||||
throw logger.bug( "No purpose for name: %s", shortNamePrefix );
|
||||
}
|
||||
|
||||
public static MPKeyPurpose forInt(final int keyPurpose) {
|
||||
|
||||
return values()[keyPurpose];
|
||||
}
|
||||
|
||||
public int toInt() {
|
||||
|
||||
return ordinal();
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.masterpassword.MPUtils.*;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MPMasterKey {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPMasterKey.class );
|
||||
|
||||
private final EnumMap<Version, byte[]> keyByVersion = new EnumMap<>( Version.class );
|
||||
private final String fullName;
|
||||
private final char[] masterPassword;
|
||||
|
||||
private boolean invalidated;
|
||||
|
||||
/**
|
||||
* @param masterPassword The characters of the user's master password. Note: this array is held by reference and its contents
|
||||
* invalidated on {@link #invalidate()}.
|
||||
*/
|
||||
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
|
||||
public MPMasterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
this.fullName = fullName;
|
||||
this.masterPassword = masterPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the master key for a user based on their name and master password.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
private byte[] masterKey(final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
Preconditions.checkArgument( masterPassword.length > 0 );
|
||||
|
||||
if (invalidated)
|
||||
throw new MPInvalidatedException();
|
||||
|
||||
byte[] key = keyByVersion.get( algorithmVersion );
|
||||
if (key == null) {
|
||||
logger.trc( "-- mpw_masterKey (algorithm: %d)", algorithmVersion.toInt() );
|
||||
logger.trc( "fullName: %s", fullName );
|
||||
logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex( idForBytes( bytesForChars( masterPassword ) ) ) );
|
||||
|
||||
keyByVersion.put( algorithmVersion, key = algorithmVersion.getAlgorithm().masterKey( fullName, masterPassword ) );
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the master key for a user based on their name and master password.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
private byte[] siteKey(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext, final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||
|
||||
byte[] masterKey = masterKey( algorithmVersion );
|
||||
|
||||
logger.trc( "-- mpw_siteKey (algorithm: %d)", algorithmVersion.toInt() );
|
||||
logger.trc( "siteName: %s", siteName );
|
||||
logger.trc( "siteCounter: %s", siteCounter );
|
||||
logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
|
||||
logger.trc( "keyContext: %s", keyContext );
|
||||
|
||||
return algorithmVersion.getAlgorithm().siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a site result token.
|
||||
*
|
||||
* @param siteName A site identifier.
|
||||
* @param siteCounter The result identifier.
|
||||
* @param keyPurpose The intended purpose for this site result.
|
||||
* @param keyContext A site-scoped result modifier.
|
||||
* @param resultType The type of result to generate.
|
||||
* @param resultParam A parameter for the resultType. For stateful result types, the output of
|
||||
* {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
|
||||
final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
byte[] masterKey = masterKey( algorithmVersion );
|
||||
byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
|
||||
|
||||
logger.trc( "-- mpw_siteResult (algorithm: %d)", algorithmVersion.toInt() );
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %s", resultParam );
|
||||
|
||||
return algorithmVersion.getAlgorithm().siteResult(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a stateful site token for persistence.
|
||||
*
|
||||
* @param siteName A site identifier.
|
||||
* @param siteCounter The result identifier.
|
||||
* @param keyPurpose The intended purpose for the site token.
|
||||
* @param keyContext A site-scoped key modifier.
|
||||
* @param resultType The type of result token to encrypt.
|
||||
* @param resultParam The result token desired from
|
||||
* {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
|
||||
final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
Preconditions.checkNotNull( resultParam );
|
||||
Preconditions.checkArgument( !resultParam.isEmpty() );
|
||||
|
||||
byte[] masterKey = masterKey( algorithmVersion );
|
||||
byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
|
||||
|
||||
logger.trc( "-- mpw_siteState (algorithm: %d)", algorithmVersion.toInt() );
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( MPAlgorithm.mpw_charset ).length, resultParam );
|
||||
|
||||
return algorithmVersion.getAlgorithm().siteState(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
|
||||
return fullName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate an identifier for the master key.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public byte[] getKeyID(final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
return idForBytes( masterKey( algorithmVersion ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe this key's secrets from memory, making the object permanently unusable.
|
||||
*/
|
||||
public void invalidate() {
|
||||
|
||||
invalidated = true;
|
||||
for (final byte[] key : keyByVersion.values())
|
||||
Arrays.fill( key, (byte) 0 );
|
||||
Arrays.fill( masterPassword, (char) 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* The algorithm iterations.
|
||||
*/
|
||||
public enum Version {
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - does math with chars whose signedness was platform-dependent.
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V0( new MPAlgorithmV0() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V1( new MPAlgorithmV1() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V2( new MPAlgorithmV2() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - no known issues.
|
||||
*/
|
||||
V3( new MPAlgorithmV3() );
|
||||
|
||||
public static final Version CURRENT = V3;
|
||||
|
||||
private final MPAlgorithm algorithm;
|
||||
|
||||
Version(final MPAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public static Version fromInt(final int algorithmVersion) {
|
||||
|
||||
return values()[algorithmVersion];
|
||||
}
|
||||
|
||||
public int toInt() {
|
||||
|
||||
return ordinal();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,252 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPResultType {
|
||||
// bit 0-3 | MPResultTypeClass | MPSiteFeature
|
||||
|
||||
/**
|
||||
* pg^VMAUBk5x3p%HP%i4=
|
||||
*/
|
||||
GeneratedMaximum( "maximum", "20 characters, contains symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ),
|
||||
new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
|
||||
MPResultTypeClass.Template, 0x0 ),
|
||||
|
||||
/**
|
||||
* BiroYena8:Kixa
|
||||
*/
|
||||
GeneratedLong( "long", "Copy-friendly, 14 characters, contains symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
|
||||
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
|
||||
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
|
||||
new MPTemplate( "CvcvnoCvccCvcv" ), new MPTemplate( "CvcvCvccnoCvcv" ),
|
||||
new MPTemplate( "CvcvCvccCvcvno" ), new MPTemplate( "CvcvnoCvcvCvcc" ),
|
||||
new MPTemplate( "CvcvCvcvnoCvcc" ), new MPTemplate( "CvcvCvcvCvccno" ),
|
||||
new MPTemplate( "CvccnoCvccCvcv" ), new MPTemplate( "CvccCvccnoCvcv" ),
|
||||
new MPTemplate( "CvccCvccCvcvno" ), new MPTemplate( "CvcvnoCvccCvcc" ),
|
||||
new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
|
||||
new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
|
||||
new MPTemplate( "CvccCvcvCvccno" ) ), //
|
||||
MPResultTypeClass.Template, 0x1 ),
|
||||
|
||||
/**
|
||||
* BirSuj0-
|
||||
*/
|
||||
GeneratedMedium( "medium", "Copy-friendly, 8 characters, contains symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "CvcnoCvc" ),
|
||||
new MPTemplate( "CvcCvcno" ) ), //
|
||||
MPResultTypeClass.Template, 0x2 ),
|
||||
|
||||
/**
|
||||
* pO98MoD0
|
||||
*/
|
||||
GeneratedBasic( "basic", "8 characters, no symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "aaanaaan" ),
|
||||
new MPTemplate( "aannaaan" ),
|
||||
new MPTemplate( "aaannaaa" ) ), //
|
||||
MPResultTypeClass.Template, 0x3 ),
|
||||
|
||||
/**
|
||||
* Bir8
|
||||
*/
|
||||
GeneratedShort( "short", "Copy-friendly, 4 characters, no symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
|
||||
MPResultTypeClass.Template, 0x4 ),
|
||||
|
||||
/**
|
||||
* 2798
|
||||
*/
|
||||
GeneratedPIN( "pin", "4 numbers.", //
|
||||
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
|
||||
MPResultTypeClass.Template, 0x5 ),
|
||||
|
||||
/**
|
||||
* birsujano
|
||||
*/
|
||||
GeneratedName( "name", "9 letter name.", //
|
||||
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
|
||||
MPResultTypeClass.Template, 0xE ),
|
||||
|
||||
/**
|
||||
* bir yennoquce fefi
|
||||
*/
|
||||
GeneratedPhrase( "phrase", "20 character sentence.", //
|
||||
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ),
|
||||
new MPTemplate( "cvc cvccvcvcv cvcv" ),
|
||||
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
|
||||
MPResultTypeClass.Template, 0xF ),
|
||||
|
||||
/**
|
||||
* Custom saved password.
|
||||
*/
|
||||
StoredPersonal( "personal", "AES-encrypted, exportable.", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ),
|
||||
|
||||
/**
|
||||
* Custom saved password that should not be exported from the device.
|
||||
*/
|
||||
StoredDevicePrivate( "device", "AES-encrypted, not exported.", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ),
|
||||
|
||||
/**
|
||||
* Derive a unique binary key.
|
||||
*/
|
||||
DeriveKey( "key", "Encryption key.", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative );
|
||||
|
||||
public static final MPResultType DEFAULT = GeneratedLong;
|
||||
|
||||
static final Logger logger = Logger.get( MPResultType.class );
|
||||
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
private final List<MPTemplate> templates;
|
||||
private final MPResultTypeClass typeClass;
|
||||
private final int typeIndex;
|
||||
private final Set<MPSiteFeature> typeFeatures;
|
||||
|
||||
MPResultType(final String shortName, final String description, final List<MPTemplate> templates,
|
||||
final MPResultTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
|
||||
|
||||
this.shortName = shortName;
|
||||
this.description = description;
|
||||
this.templates = templates;
|
||||
this.typeClass = typeClass;
|
||||
this.typeIndex = typeIndex;
|
||||
|
||||
ImmutableSet.Builder<MPSiteFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
||||
for (final MPSiteFeature typeFeature : typeFeatures) {
|
||||
typeFeaturesBuilder.add( typeFeature );
|
||||
}
|
||||
this.typeFeatures = typeFeaturesBuilder.build();
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
public MPResultTypeClass getTypeClass() {
|
||||
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
public Set<MPSiteFeature> getTypeFeatures() {
|
||||
|
||||
return typeFeatures;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
int mask = typeIndex | typeClass.getMask();
|
||||
for (final MPSiteFeature typeFeature : typeFeatures)
|
||||
mask |= typeFeature.getMask();
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shortNamePrefix The name for the type to look up. It is a case insensitive prefix of the type's short name.
|
||||
*
|
||||
* @return The type registered with the given name.
|
||||
*/
|
||||
@Nullable
|
||||
@Contract("!null -> !null")
|
||||
public static MPResultType forName(@Nullable final String shortNamePrefix) {
|
||||
|
||||
if (shortNamePrefix == null)
|
||||
return null;
|
||||
|
||||
for (final MPResultType type : values())
|
||||
if (type.getShortName().toLowerCase( Locale.ROOT ).startsWith( shortNamePrefix.toLowerCase( Locale.ROOT ) ))
|
||||
return type;
|
||||
|
||||
throw logger.bug( "No type for name: %s", shortNamePrefix );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param typeClass The class for which we look up types.
|
||||
*
|
||||
* @return All types that support the given class.
|
||||
*/
|
||||
public static ImmutableList<MPResultType> forClass(final MPResultTypeClass typeClass) {
|
||||
|
||||
ImmutableList.Builder<MPResultType> types = ImmutableList.builder();
|
||||
for (final MPResultType type : values())
|
||||
if (type.getTypeClass() == typeClass)
|
||||
types.add( type );
|
||||
|
||||
return types.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type The type for which we look up types.
|
||||
*
|
||||
* @return The type registered with the given type.
|
||||
*/
|
||||
public static MPResultType forType(final int type) {
|
||||
|
||||
for (final MPResultType resultType : values())
|
||||
if (resultType.getType() == type)
|
||||
return resultType;
|
||||
|
||||
throw logger.bug( "No type: %s", type );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mask The type mask for which we look up types.
|
||||
*
|
||||
* @return All types that support the given mask's class & features.
|
||||
*/
|
||||
public static ImmutableList<MPResultType> forMask(final int mask) {
|
||||
|
||||
int typeMask = mask & ~0xF; // Ignore resultType bit 0-3
|
||||
|
||||
ImmutableList.Builder<MPResultType> types = ImmutableList.builder();
|
||||
for (final MPResultType resultType : values())
|
||||
if (((resultType.getType() & ~0xF) & typeMask) != 0)
|
||||
types.add( resultType );
|
||||
|
||||
return types.build();
|
||||
}
|
||||
|
||||
public MPTemplate getTemplateAtRollingIndex(final int templateIndex) {
|
||||
return templates.get( templateIndex % templates.size() );
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPResultTypeClass {
|
||||
// bit 4 - 9
|
||||
|
||||
/**
|
||||
* Use the site key to generate a password from a template.
|
||||
*/
|
||||
Template( 1 << 4 ),
|
||||
/**
|
||||
* Use the site key to encrypt and decrypt a stateful entity.
|
||||
*/
|
||||
Stateful( 1 << 5 ),
|
||||
/**
|
||||
* Use the site key to derive a site-specific object.
|
||||
*/
|
||||
Derive( 1 << 6 );
|
||||
|
||||
private final int mask;
|
||||
|
||||
MPResultTypeClass(final int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
public int getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPSiteFeature {
|
||||
// bit 10 - 15
|
||||
|
||||
/**
|
||||
* Export the key-protected content data.
|
||||
*/
|
||||
ExportContent( 1 << 10 ),
|
||||
|
||||
/**
|
||||
* Never export content.
|
||||
*/
|
||||
DevicePrivate( 1 << 11 ),
|
||||
|
||||
/**
|
||||
* Don't use this as the primary authentication result type.
|
||||
*/
|
||||
Alternative( 1 << 12 );
|
||||
|
||||
MPSiteFeature(final int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
public int getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplate extends MetaObject implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String templateString;
|
||||
private final List<MPTemplateCharacterClass> template;
|
||||
|
||||
MPTemplate(@NonNls final String templateString) {
|
||||
|
||||
ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.builder();
|
||||
for (int i = 0; i < templateString.length(); ++i)
|
||||
builder.add( MPTemplateCharacterClass.forIdentifier( templateString.charAt( i ) ) );
|
||||
|
||||
this.templateString = templateString;
|
||||
template = builder.build();
|
||||
}
|
||||
|
||||
public String getTemplateString() {
|
||||
return templateString;
|
||||
}
|
||||
|
||||
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
|
||||
|
||||
return template.get( index );
|
||||
}
|
||||
|
||||
public int length() {
|
||||
|
||||
return template.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{MPTemplate: %s}", templateString );
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPTemplateCharacterClass {
|
||||
|
||||
UpperVowel( 'V', "AEIOU" ),
|
||||
UpperConsonant( 'C', "BCDFGHJKLMNPQRSTVWXYZ" ),
|
||||
LowerVowel( 'v', "aeiou" ),
|
||||
LowerConsonant( 'c', "bcdfghjklmnpqrstvwxyz" ),
|
||||
UpperAlphanumeric( 'A', "AEIOUBCDFGHJKLMNPQRSTVWXYZ" ),
|
||||
Alphanumeric( 'a', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz" ),
|
||||
Numeric( 'n', "0123456789" ),
|
||||
Other( 'o', "@&%?,=[]_:-+*$#!'^~;()/." ),
|
||||
Any( 'x', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()" ),
|
||||
Space( ' ', " " );
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTemplateCharacterClass.class );
|
||||
|
||||
private final char identifier;
|
||||
private final char[] characters;
|
||||
|
||||
MPTemplateCharacterClass(final char identifier, @NonNls final String characters) {
|
||||
|
||||
this.identifier = identifier;
|
||||
this.characters = characters.toCharArray();
|
||||
}
|
||||
|
||||
public char getIdentifier() {
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public char getCharacterAtRollingIndex(final int index) {
|
||||
|
||||
return characters[index % characters.length];
|
||||
}
|
||||
|
||||
public static MPTemplateCharacterClass forIdentifier(final char identifier) {
|
||||
for (final MPTemplateCharacterClass characterClass : values())
|
||||
if (characterClass.getIdentifier() == identifier)
|
||||
return characterClass;
|
||||
|
||||
throw logger.bug( "No character class defined for identifier: %s", identifier );
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
public final class MPUtils {
|
||||
|
||||
public static byte[] bytesForInt(final int number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithm.mpw_byteOrder ).putInt( number ).array();
|
||||
}
|
||||
|
||||
public static byte[] bytesForInt(final UnsignedInteger number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithm.mpw_byteOrder ).putInt( number.intValue() ).array();
|
||||
}
|
||||
|
||||
public static byte[] bytesForChars(final char[] characters) {
|
||||
ByteBuffer byteBuffer = MPAlgorithm.mpw_charset.encode( CharBuffer.wrap( characters ) );
|
||||
|
||||
byte[] bytes = new byte[byteBuffer.remaining()];
|
||||
byteBuffer.get( bytes );
|
||||
|
||||
Arrays.fill( byteBuffer.array(), (byte) 0 );
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static byte[] idForBytes(final byte[] bytes) {
|
||||
return MPAlgorithm.mpw_hash.of( bytes );
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
|
||||
@ParametersAreNonnullByDefault package com.lyndir.masterpassword;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@ -1,18 +0,0 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.ltgt.apt' version '0.9'
|
||||
}
|
||||
|
||||
description = 'Master Password Site Model'
|
||||
|
||||
dependencies {
|
||||
compile project(':masterpassword-algorithm')
|
||||
|
||||
compile group: 'joda-time', name: 'joda-time', version:'2.4'
|
||||
compileOnly group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
||||
apt group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
||||
|
||||
testCompile group: 'org.testng', name: 'testng', version:'6.8.5'
|
||||
testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2'
|
||||
}
|
||||
test.useTestNG()
|
@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Site Model</name>
|
||||
<description>A persistence model for Master Password sites.</description>
|
||||
|
||||
<artifactId>masterpassword-model</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value</artifactId>
|
||||
<version>1.0-rc1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- TESTING -->
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -1,205 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.Instant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-05
|
||||
*/
|
||||
public class MPFileSite extends MPSite {
|
||||
|
||||
private final MPFileUser user;
|
||||
private String siteName;
|
||||
@Nullable
|
||||
private String siteContent;
|
||||
private UnsignedInteger siteCounter;
|
||||
private MPResultType resultType;
|
||||
private MPMasterKey.Version algorithmVersion;
|
||||
|
||||
@Nullable
|
||||
private String loginContent;
|
||||
@Nullable
|
||||
private MPResultType loginType;
|
||||
|
||||
@Nullable
|
||||
private String url;
|
||||
private int uses;
|
||||
private Instant lastUsed;
|
||||
|
||||
public MPFileSite(final MPFileUser user, final String siteName) {
|
||||
this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT, MPMasterKey.Version.CURRENT );
|
||||
}
|
||||
|
||||
public MPFileSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType,
|
||||
final MPMasterKey.Version algorithmVersion) {
|
||||
this.user = user;
|
||||
this.siteName = siteName;
|
||||
this.siteCounter = siteCounter;
|
||||
this.resultType = resultType;
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
this.lastUsed = new Instant();
|
||||
}
|
||||
|
||||
protected MPFileSite(final MPFileUser user, final String siteName, @Nullable final String siteContent,
|
||||
final UnsignedInteger siteCounter,
|
||||
final MPResultType resultType, final MPMasterKey.Version algorithmVersion,
|
||||
@Nullable final String loginContent, @Nullable final MPResultType loginType,
|
||||
@Nullable final String url, final int uses, final Instant lastUsed) {
|
||||
this.user = user;
|
||||
this.siteName = siteName;
|
||||
this.siteContent = siteContent;
|
||||
this.siteCounter = siteCounter;
|
||||
this.resultType = resultType;
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
this.loginContent = loginContent;
|
||||
this.loginType = loginType;
|
||||
this.url = url;
|
||||
this.uses = uses;
|
||||
this.lastUsed = lastUsed;
|
||||
}
|
||||
|
||||
public String resultFor(final MPMasterKey masterKey)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
return resultFor( masterKey, MPKeyPurpose.Authentication, null );
|
||||
}
|
||||
|
||||
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
return resultFor( masterKey, keyPurpose, keyContext, getSiteContent() );
|
||||
}
|
||||
|
||||
public String loginFor(final MPMasterKey masterKey)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
if (loginType == null)
|
||||
loginType = MPResultType.GeneratedName;
|
||||
|
||||
return loginFor( masterKey, loginType, loginContent );
|
||||
}
|
||||
|
||||
public MPFileUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSiteName() {
|
||||
return siteName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiteName(final String siteName) {
|
||||
this.siteName = siteName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSiteContent() {
|
||||
return siteContent;
|
||||
}
|
||||
|
||||
public void setSitePassword(final MPMasterKey masterKey, @Nullable final MPResultType resultType, @Nullable final String result)
|
||||
throws MPInvalidatedException {
|
||||
this.resultType = resultType;
|
||||
if (result == null)
|
||||
this.siteContent = null;
|
||||
else
|
||||
this.siteContent = masterKey.siteState(
|
||||
getSiteName(), getSiteCounter(), MPKeyPurpose.Authentication, null, getResultType(), result, getAlgorithmVersion() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return siteCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||
this.siteCounter = siteCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType getResultType() {
|
||||
return resultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResultType(final MPResultType resultType) {
|
||||
this.resultType = resultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
return algorithmVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MPResultType getLoginType() {
|
||||
return loginType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLoginContent() {
|
||||
return loginContent;
|
||||
}
|
||||
|
||||
public void setLoginName(final MPMasterKey masterKey, @Nullable final MPResultType loginType, @Nullable final String result)
|
||||
throws MPInvalidatedException {
|
||||
this.loginType = loginType;
|
||||
if (this.loginType != null)
|
||||
if (result == null)
|
||||
this.loginContent = null;
|
||||
else
|
||||
this.loginContent = masterKey.siteState(
|
||||
siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, this.loginType, result, algorithmVersion );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(@Nullable final String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getUses() {
|
||||
return uses;
|
||||
}
|
||||
|
||||
public Instant getLastUsed() {
|
||||
return lastUsed;
|
||||
}
|
||||
|
||||
public void use() {
|
||||
uses++;
|
||||
lastUsed = new Instant();
|
||||
user.use();
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public class MPFileUser extends MPUser<MPFileSite> implements Comparable<MPFileUser> {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPFileUser.class );
|
||||
|
||||
private final String fullName;
|
||||
private final Collection<MPFileSite> sites = Sets.newHashSet();
|
||||
|
||||
@Nullable
|
||||
private byte[] keyID;
|
||||
private MPMasterKey.Version algorithmVersion;
|
||||
|
||||
private int avatar;
|
||||
private MPResultType defaultType;
|
||||
private ReadableInstant lastUsed;
|
||||
|
||||
public MPFileUser(final String fullName) {
|
||||
this( fullName, null, MPMasterKey.Version.CURRENT );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion) {
|
||||
this( fullName, keyID, algorithmVersion, 0, MPResultType.DEFAULT, new Instant() );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion, final int avatar,
|
||||
final MPResultType defaultType, final ReadableInstant lastUsed) {
|
||||
this.fullName = fullName;
|
||||
this.keyID = (keyID == null)? null: keyID.clone();
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
this.avatar = avatar;
|
||||
this.defaultType = defaultType;
|
||||
this.lastUsed = lastUsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
return algorithmVersion;
|
||||
}
|
||||
|
||||
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(final int avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public MPResultType getDefaultType() {
|
||||
return defaultType;
|
||||
}
|
||||
|
||||
public void setDefaultType(final MPResultType defaultType) {
|
||||
this.defaultType = defaultType;
|
||||
}
|
||||
|
||||
public ReadableInstant getLastUsed() {
|
||||
return lastUsed;
|
||||
}
|
||||
|
||||
public void use() {
|
||||
lastUsed = new Instant();
|
||||
}
|
||||
|
||||
public Iterable<MPFileSite> getSites() {
|
||||
return sites;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSite(final MPFileSite site) {
|
||||
sites.add( site );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSite(final MPFileSite site) {
|
||||
sites.remove( site );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MPFileSite> findSites(final String query) {
|
||||
ImmutableList.Builder<MPFileSite> results = ImmutableList.builder();
|
||||
for (final MPFileSite site : getSites())
|
||||
if (site.getSiteName().startsWith( query ))
|
||||
results.add( site );
|
||||
|
||||
return results.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an authentication attempt against the keyID for this user.
|
||||
*
|
||||
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
|
||||
*
|
||||
* @param masterPassword The password to authenticate with.
|
||||
*
|
||||
* @return The master key for the user if authentication was successful.
|
||||
*
|
||||
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPMasterKey authenticate(final char[] masterPassword)
|
||||
throws MPIncorrectMasterPasswordException {
|
||||
try {
|
||||
key = new MPMasterKey( getFullName(), masterPassword );
|
||||
if ((keyID == null) || (keyID.length == 0))
|
||||
keyID = key.getKeyID( algorithmVersion );
|
||||
else if (!Arrays.equals( key.getKeyID( algorithmVersion ), keyID ))
|
||||
throw new MPIncorrectMasterPasswordException( this );
|
||||
|
||||
return key;
|
||||
}
|
||||
catch (final MPInvalidatedException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
|
||||
void save()
|
||||
throws MPInvalidatedException {
|
||||
MPFileUserManager.get().save( this, getMasterKey() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final MPFileUser o) {
|
||||
int comparison = getLastUsed().compareTo( o.getLastUsed() );
|
||||
if (comparison == 0)
|
||||
comparison = getFullName().compareTo( o.getFullName() );
|
||||
|
||||
return comparison;
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.io.CharSink;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.io.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}.
|
||||
*
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public class MPFileUserManager extends MPUserManager {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPFileUserManager.class );
|
||||
private static final MPFileUserManager instance;
|
||||
|
||||
static {
|
||||
String rcDir = System.getenv( MPConstant.env_rcDir );
|
||||
if (rcDir != null)
|
||||
instance = create( new File( rcDir ) );
|
||||
else
|
||||
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
|
||||
}
|
||||
|
||||
private final File userFilesDirectory;
|
||||
|
||||
public static MPFileUserManager get() {
|
||||
MPUserManager.instance = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static MPFileUserManager create(final File userFilesDirectory) {
|
||||
return new MPFileUserManager( userFilesDirectory );
|
||||
}
|
||||
|
||||
protected MPFileUserManager(final File userFilesDirectory) {
|
||||
|
||||
super( unmarshallUsers( userFilesDirectory ) );
|
||||
this.userFilesDirectory = userFilesDirectory;
|
||||
}
|
||||
|
||||
private static Iterable<MPFileUser> unmarshallUsers(final File userFilesDirectory) {
|
||||
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
|
||||
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
return FluentIterable.from( listUserFiles( userFilesDirectory ) ).transform( new Function<File, MPFileUser>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public MPFileUser apply(@Nullable final File file) {
|
||||
try {
|
||||
return new MPFlatUnmarshaller().unmarshall( Preconditions.checkNotNull( file ) );
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.err( e, "Couldn't read user from: %s", file );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} ).filter( Predicates.notNull() );
|
||||
}
|
||||
|
||||
private static ImmutableList<File> listUserFiles(final File userFilesDirectory) {
|
||||
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(final File dir, final String name) {
|
||||
return name.endsWith( ".mpsites" );
|
||||
}
|
||||
} ), new File[0] ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(final MPFileUser user) {
|
||||
super.deleteUser( user );
|
||||
|
||||
// Remove deleted users.
|
||||
File userFile = getUserFile( user );
|
||||
if (userFile.exists() && !userFile.delete())
|
||||
logger.err( "Couldn't delete file: %s", userFile );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current user state to disk.
|
||||
*/
|
||||
public void save(final MPFileUser user, final MPMasterKey masterKey)
|
||||
throws MPInvalidatedException {
|
||||
try {
|
||||
new CharSink() {
|
||||
@Override
|
||||
public Writer openStream()
|
||||
throws IOException {
|
||||
return new OutputStreamWriter( new FileOutputStream( getUserFile( user ) ), Charsets.UTF_8 );
|
||||
}
|
||||
}.write( new MPFlatMarshaller().marshall( user, masterKey, MPMarshaller.ContentMode.PROTECTED ) );
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.err( e, "Unable to save sites for user: %s", user );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private File getUserFile(final MPFileUser user) {
|
||||
return new File( userFilesDirectory, user.getFullName() + ".mpsites" );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The location on the file system where the user models are stored.
|
||||
*/
|
||||
public File getPath() {
|
||||
return userFilesDirectory;
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import org.joda.time.Instant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
public class MPFlatMarshaller implements MPMarshaller {
|
||||
|
||||
private static final int FORMAT = 1;
|
||||
|
||||
@Override
|
||||
public String marshall(final MPFileUser user, final MPMasterKey masterKey, final ContentMode contentMode)
|
||||
throws MPInvalidatedException {
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append( "# Master Password site export\n" );
|
||||
content.append( "# " ).append( contentMode.description() ).append( '\n' );
|
||||
content.append( "# \n" );
|
||||
content.append( "##\n" );
|
||||
content.append( "# Format: " ).append( FORMAT ).append( '\n' );
|
||||
content.append( "# Date: " ).append( MPConstant.dateTimeFormatter.print( new Instant() ) ).append( '\n' );
|
||||
content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
|
||||
content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
|
||||
content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
|
||||
content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
|
||||
content.append( "# Algorithm: " ).append( MPMasterKey.Version.CURRENT.toInt() ).append( '\n' );
|
||||
content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
|
||||
content.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
|
||||
content.append( "##\n" );
|
||||
content.append( "#\n" );
|
||||
content.append( "# Last Times Password Login\t Site\tSite\n" );
|
||||
content.append( "# used used type name\t name\tpassword\n" );
|
||||
|
||||
for (final MPFileSite site : user.getSites()) {
|
||||
String loginName = site.getLoginContent();
|
||||
String password = site.getSiteContent();
|
||||
if (!contentMode.isRedacted()) {
|
||||
loginName = site.loginFor( masterKey );
|
||||
password = site.resultFor( masterKey );
|
||||
}
|
||||
|
||||
content.append( strf( "%s %8d %8s %25s\t%25s\t%s\n", //
|
||||
MPConstant.dateTimeFormatter.print( site.getLastUsed() ), // lastUsed
|
||||
site.getUses(), // uses
|
||||
strf( "%d:%d:%d", //
|
||||
site.getResultType().getType(), // type
|
||||
site.getAlgorithmVersion().toInt(), // algorithm
|
||||
site.getSiteCounter().intValue() ), // counter
|
||||
ifNotNullElse( loginName, "" ), // loginName
|
||||
site.getSiteName(), // siteName
|
||||
ifNotNullElse( password, "" ) // password
|
||||
) );
|
||||
}
|
||||
|
||||
return content.toString();
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
|
||||
private static final Pattern[] unmarshallFormats = {
|
||||
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
|
||||
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
|
||||
private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" );
|
||||
private static final Pattern colon = Pattern.compile( ":" );
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final File file)
|
||||
throws IOException {
|
||||
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
|
||||
return unmarshall( CharStreams.toString( reader ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final String content) {
|
||||
MPFileUser user = null;
|
||||
byte[] keyID = null;
|
||||
String fullName = null;
|
||||
int mpVersion = 0, importFormat = 0, avatar = 0;
|
||||
boolean clearContent = false, headerStarted = false;
|
||||
MPResultType defaultType = MPResultType.DEFAULT;
|
||||
|
||||
//noinspection HardcodedLineSeparator
|
||||
for (final String line : Splitter.on( CharMatcher.anyOf( "\r\n" ) ).omitEmptyStrings().split( content ))
|
||||
// Header delimitor.
|
||||
if (line.startsWith( "##" ))
|
||||
if (!headerStarted)
|
||||
// Starts the header.
|
||||
headerStarted = true;
|
||||
else
|
||||
// Ends the header.
|
||||
user = new MPFileUser( fullName, keyID, MPMasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
|
||||
|
||||
// Comment.
|
||||
else if (line.startsWith( "#" )) {
|
||||
if (headerStarted && (user == null)) {
|
||||
// In header.
|
||||
Matcher headerMatcher = headerFormat.matcher( line );
|
||||
if (headerMatcher.matches()) {
|
||||
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
|
||||
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
||||
fullName = value;
|
||||
else if ("Key ID".equalsIgnoreCase( name ))
|
||||
keyID = CodeUtils.decodeHex( value );
|
||||
else if ("Algorithm".equalsIgnoreCase( name ))
|
||||
mpVersion = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Format".equalsIgnoreCase( name ))
|
||||
importFormat = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Avatar".equalsIgnoreCase( name ))
|
||||
avatar = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Passwords".equalsIgnoreCase( name ))
|
||||
clearContent = "visible".equalsIgnoreCase( value );
|
||||
else if ("Default Type".equalsIgnoreCase( name ))
|
||||
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No comment.
|
||||
else if (user != null) {
|
||||
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( line );
|
||||
if (!siteMatcher.matches())
|
||||
return null;
|
||||
|
||||
MPFileSite site;
|
||||
switch (importFormat) {
|
||||
case 0:
|
||||
site = new MPFileSite( user, //
|
||||
siteMatcher.group( 5 ), siteMatcher.group( 6 ), MPFileSite.DEFAULT_COUNTER,
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
|
||||
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
break;
|
||||
|
||||
case 1:
|
||||
site = new MPFileSite( user, //
|
||||
siteMatcher.group( 7 ), siteMatcher.group( 8 ),
|
||||
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
|
||||
siteMatcher.group( 6 ), MPResultType.GeneratedName, null,
|
||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Unexpected format: " + importFormat );
|
||||
}
|
||||
|
||||
user.addSite( site );
|
||||
}
|
||||
|
||||
return Preconditions.checkNotNull( user, "No full header found in import file." );
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-17
|
||||
*/
|
||||
public class MPIncorrectMasterPasswordException extends Exception {
|
||||
|
||||
private final MPFileUser user;
|
||||
|
||||
public MPIncorrectMasterPasswordException(final MPFileUser user) {
|
||||
super( "Incorrect master password for user: " + user.getFullName() );
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public MPFileUser getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.lyndir.masterpassword.MPMasterKey;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
public class MPJSONMarshaller implements MPMarshaller {
|
||||
|
||||
@Override
|
||||
public String marshall(final MPFileUser user, final MPMasterKey masterKey, final ContentMode contentMode) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
public class MPJSONUnmarshaller implements MPUnmarshaller {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final File file)
|
||||
throws IOException {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final String content) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
public enum MPMarshalFormat {
|
||||
/**
|
||||
* Marshal using the line-based plain-text format.
|
||||
*/
|
||||
Flat {
|
||||
@Override
|
||||
public MPMarshaller marshaller() {
|
||||
return new MPFlatMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPUnmarshaller unmarshaller() {
|
||||
return new MPFlatUnmarshaller();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Marshal using the JSON structured format.
|
||||
*/
|
||||
JSON {
|
||||
@Override
|
||||
public MPMarshaller marshaller() {
|
||||
return new MPJSONMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPUnmarshaller unmarshaller() {
|
||||
return new MPJSONUnmarshaller();
|
||||
}
|
||||
};
|
||||
|
||||
public static final MPMarshalFormat DEFAULT = JSON;
|
||||
|
||||
public abstract MPMarshaller marshaller();
|
||||
|
||||
public abstract MPUnmarshaller unmarshaller();
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.lyndir.masterpassword.MPInvalidatedException;
|
||||
import com.lyndir.masterpassword.MPMasterKey;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public interface MPMarshaller {
|
||||
|
||||
String marshall(MPFileUser user, MPMasterKey masterKey, ContentMode contentMode)
|
||||
throws MPInvalidatedException;
|
||||
|
||||
enum ContentMode {
|
||||
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ),
|
||||
VISIBLE( "Export of site names and passwords in clear-text." );
|
||||
|
||||
private final String description;
|
||||
private boolean redacted;
|
||||
|
||||
ContentMode(final String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isRedacted() {
|
||||
return redacted;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-16
|
||||
*/
|
||||
public abstract class MPSite {
|
||||
|
||||
public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.ONE;
|
||||
|
||||
public abstract String getSiteName();
|
||||
|
||||
public abstract void setSiteName(String siteName);
|
||||
|
||||
public abstract UnsignedInteger getSiteCounter();
|
||||
|
||||
public abstract void setSiteCounter(UnsignedInteger siteCounter);
|
||||
|
||||
public abstract MPResultType getResultType();
|
||||
|
||||
public abstract void setResultType(MPResultType resultType);
|
||||
|
||||
public abstract MPMasterKey.Version getAlgorithmVersion();
|
||||
|
||||
public abstract void setAlgorithmVersion(MPMasterKey.Version algorithmVersion);
|
||||
|
||||
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final String siteContent)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
return masterKey.siteResult(
|
||||
getSiteName(), getSiteCounter(), keyPurpose, keyContext, getResultType(), siteContent, getAlgorithmVersion() );
|
||||
}
|
||||
|
||||
public String loginFor(final MPMasterKey masterKey, final MPResultType loginType, @Nullable final String loginContent)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
return masterKey.siteResult(
|
||||
getSiteName(), DEFAULT_COUNTER, MPKeyPurpose.Identification, null, loginType, loginContent, getAlgorithmVersion() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getSiteName(), ((MPSite) obj).getSiteName() ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getSiteName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public class MPSiteResult {
|
||||
|
||||
private final MPFileSite site;
|
||||
|
||||
public MPSiteResult(final MPFileSite site) {
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
public MPFileSite getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (this == obj) || ((obj instanceof MPSiteResult) && Objects.equals( site, ((MPSiteResult) obj).site ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( site );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{MPSiteResult: %s}", site );
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import java.io.*;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public interface MPUnmarshaller {
|
||||
|
||||
@Nonnull
|
||||
MPFileUser unmarshall(@Nonnull File file)
|
||||
throws IOException;
|
||||
|
||||
@Nonnull
|
||||
MPFileUser unmarshall(@Nonnull String content);
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.masterpassword.MPInvalidatedException;
|
||||
import com.lyndir.masterpassword.MPMasterKey;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class MPUser<S extends MPSite> {
|
||||
|
||||
@Nullable
|
||||
protected MPMasterKey key;
|
||||
|
||||
public abstract String getFullName();
|
||||
|
||||
public boolean isMasterKeyAvailable() {
|
||||
return key != null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPMasterKey getMasterKey() {
|
||||
return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
|
||||
}
|
||||
|
||||
public String exportKeyID()
|
||||
throws MPInvalidatedException {
|
||||
return CodeUtils.encodeHex( getMasterKey().getKeyID( getAlgorithmVersion() ) );
|
||||
}
|
||||
|
||||
public abstract MPMasterKey.Version getAlgorithmVersion();
|
||||
|
||||
public int getAvatar() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public abstract void addSite(S site);
|
||||
|
||||
public abstract void deleteSite(S site);
|
||||
|
||||
public abstract Collection<S> findSites(String query);
|
||||
|
||||
@Nonnull
|
||||
public abstract MPMasterKey authenticate(char[] masterPassword)
|
||||
throws MPIncorrectMasterPasswordException;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getFullName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( getFullName(), ((MPUser<?>) obj).getFullName() ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{%s: %s}", getClass().getSimpleName(), getFullName() );
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import com.lyndir.masterpassword.MPInvalidatedException;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-05
|
||||
*/
|
||||
public abstract class MPUserManager {
|
||||
|
||||
private final Map<String, MPFileUser> usersByName = Maps.newHashMap();
|
||||
static MPUserManager instance;
|
||||
|
||||
public static MPUserManager get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected MPUserManager(final Iterable<MPFileUser> users) {
|
||||
for (final MPFileUser user : users)
|
||||
usersByName.put( user.getFullName(), user );
|
||||
}
|
||||
|
||||
public SortedSet<MPFileUser> getUsers() {
|
||||
return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() );
|
||||
}
|
||||
|
||||
public MPFileUser getUserNamed(final String fullName) {
|
||||
return usersByName.get( fullName );
|
||||
}
|
||||
|
||||
public void addUser(final MPFileUser user) {
|
||||
usersByName.put( user.getFullName(), user );
|
||||
}
|
||||
|
||||
public void deleteUser(final MPFileUser user) {
|
||||
usersByName.remove( user.getFullName() );
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@ -1,13 +0,0 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
description = 'Master Password Test Suite'
|
||||
|
||||
dependencies {
|
||||
compile project(':masterpassword-algorithm')
|
||||
|
||||
testCompile group: 'org.testng', name: 'testng', version:'6.8.5'
|
||||
testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2'
|
||||
}
|
||||
test.useTestNG()
|
@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Test Suite</name>
|
||||
<description>The standard test suite to ensure the Master Password algorithm is operating as it should</description>
|
||||
|
||||
<artifactId>masterpassword-tests</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TESTING -->
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -1,205 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.xml.parsers.*;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.ext.DefaultHandler2;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2015-12-22
|
||||
*/
|
||||
@SuppressWarnings({ "HardCodedStringLiteral", "ProhibitedExceptionDeclared" })
|
||||
public class MPTestSuite implements Callable<Boolean> {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTestSuite.class );
|
||||
private static final String DEFAULT_RESOURCE_NAME = "mpw_tests.xml";
|
||||
|
||||
private final MPTests tests;
|
||||
private Listener listener;
|
||||
|
||||
public MPTestSuite()
|
||||
throws UnavailableException {
|
||||
this( DEFAULT_RESOURCE_NAME );
|
||||
}
|
||||
|
||||
public MPTestSuite(final String resourceName)
|
||||
throws UnavailableException {
|
||||
try {
|
||||
tests = new MPTests();
|
||||
tests.cases = Lists.newLinkedList();
|
||||
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
|
||||
parser.parse( Thread.currentThread().getContextClassLoader().getResourceAsStream( resourceName ), new DefaultHandler2() {
|
||||
private final Deque<String> currentTags = Lists.newLinkedList();
|
||||
private final Deque<StringBuilder> currentTexts = Lists.newLinkedList();
|
||||
private MPTests.Case currentCase;
|
||||
|
||||
@Override
|
||||
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
|
||||
throws SAXException {
|
||||
super.startElement( uri, localName, qName, attributes );
|
||||
currentTags.push( qName );
|
||||
currentTexts.push( new StringBuilder() );
|
||||
|
||||
if ("case".equals( qName )) {
|
||||
currentCase = new MPTests.Case();
|
||||
currentCase.identifier = attributes.getValue( "id" );
|
||||
currentCase.parent = attributes.getValue( "parent" );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(final String uri, final String localName, final String qName)
|
||||
throws SAXException {
|
||||
super.endElement( uri, localName, qName );
|
||||
Preconditions.checkState( qName.equals( currentTags.pop() ) );
|
||||
String text = currentTexts.pop().toString();
|
||||
|
||||
if ("case".equals( qName ))
|
||||
tests.cases.add( currentCase );
|
||||
if ("algorithm".equals( qName ))
|
||||
currentCase.algorithm = ConversionUtils.toInteger( text ).orNull();
|
||||
if ("fullName".equals( qName ))
|
||||
currentCase.fullName = text;
|
||||
if ("masterPassword".equals( qName ))
|
||||
currentCase.masterPassword = text;
|
||||
if ("keyID".equals( qName ))
|
||||
currentCase.keyID = text;
|
||||
if ("siteName".equals( qName ))
|
||||
currentCase.siteName = text;
|
||||
if ("siteCounter".equals( qName ))
|
||||
currentCase.siteCounter = text.isEmpty()? null: UnsignedInteger.valueOf( text );
|
||||
if ("resultType".equals( qName ))
|
||||
currentCase.resultType = text;
|
||||
if ("keyPurpose".equals( qName ))
|
||||
currentCase.keyPurpose = text;
|
||||
if ("keyContext".equals( qName ))
|
||||
currentCase.keyContext = text;
|
||||
if ("result".equals( qName ))
|
||||
currentCase.result = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(final char[] ch, final int start, final int length)
|
||||
throws SAXException {
|
||||
super.characters( ch, start, length );
|
||||
|
||||
currentTexts.peek().append( ch, start, length );
|
||||
}
|
||||
} );
|
||||
}
|
||||
catch (IllegalArgumentException | ParserConfigurationException | SAXException | IOException e) {
|
||||
throw new UnavailableException( e );
|
||||
}
|
||||
|
||||
for (final MPTests.Case testCase : tests.getCases())
|
||||
testCase.initializeParentHierarchy( tests );
|
||||
}
|
||||
|
||||
public void setListener(final Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public MPTests getTests() {
|
||||
return tests;
|
||||
}
|
||||
|
||||
public boolean forEach(final String testName, final TestCase testFunction)
|
||||
throws Exception {
|
||||
List<MPTests.Case> cases = tests.getCases();
|
||||
for (int c = 0; c < cases.size(); c++) {
|
||||
MPTests.Case testCase = cases.get( c );
|
||||
if (testCase.getResult().isEmpty())
|
||||
continue;
|
||||
|
||||
progress( Logger.Target.INFO, c, cases.size(), //
|
||||
"[%s] on %s...", testName, testCase.getIdentifier() );
|
||||
|
||||
if (!testFunction.run( testCase )) {
|
||||
progress( Logger.Target.ERROR, cases.size(), cases.size(), //
|
||||
"[%s] on %s: FAILED!", testName, testCase.getIdentifier() );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
progress( Logger.Target.INFO, c + 1, cases.size(), //
|
||||
"[%s] on %s: passed!", testName, testCase.getIdentifier() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void progress(final Logger.Target target, final int current, final int max, final String format, final Object... args) {
|
||||
logger.log( target, format, args );
|
||||
|
||||
if (listener != null)
|
||||
listener.progress( current, max, format, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean call()
|
||||
throws Exception {
|
||||
return forEach( "mpw", new TestCase() {
|
||||
@Override
|
||||
public boolean run(final MPTests.Case testCase)
|
||||
throws Exception {
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword().toCharArray() );
|
||||
String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), testCase.getResultType(),
|
||||
null, testCase.getAlgorithm() );
|
||||
|
||||
return testCase.getResult().equals( sitePassword );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public static class UnavailableException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UnavailableException(final Throwable cause) {
|
||||
super( cause );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void progress(int current, int max, String messageFormat, Object... args);
|
||||
}
|
||||
|
||||
|
||||
public interface TestCase {
|
||||
|
||||
boolean run(MPTests.Case testCase)
|
||||
throws Exception;
|
||||
}
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.*;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-05
|
||||
*/
|
||||
public class MPTests {
|
||||
|
||||
private static final String ID_DEFAULT = "default";
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTests.class );
|
||||
|
||||
List<Case> cases;
|
||||
|
||||
@Nonnull
|
||||
public List<Case> getCases() {
|
||||
return checkNotNull( cases );
|
||||
}
|
||||
|
||||
public Case getCase(final String identifier) {
|
||||
for (final Case testCase : getCases())
|
||||
if (identifier.equals( testCase.getIdentifier() ))
|
||||
return testCase;
|
||||
|
||||
throw new IllegalArgumentException( strf( "No case for identifier: %s", identifier ) );
|
||||
}
|
||||
|
||||
public Case getDefaultCase() {
|
||||
try {
|
||||
return getCase( ID_DEFAULT );
|
||||
}
|
||||
catch (final IllegalArgumentException e) {
|
||||
throw new IllegalStateException( strf( "Missing default case in test suite. Add a case with id: %d", ID_DEFAULT ), e );
|
||||
}
|
||||
}
|
||||
|
||||
public static class Case {
|
||||
|
||||
String identifier;
|
||||
String parent;
|
||||
Integer algorithm;
|
||||
String fullName;
|
||||
String masterPassword;
|
||||
String keyID;
|
||||
String siteName;
|
||||
UnsignedInteger siteCounter;
|
||||
String resultType;
|
||||
String keyPurpose;
|
||||
String keyContext;
|
||||
String result;
|
||||
|
||||
private transient Case parentCase;
|
||||
|
||||
public void initializeParentHierarchy(final MPTests tests) {
|
||||
|
||||
if (parent != null) {
|
||||
parentCase = tests.getCase( parent );
|
||||
parentCase.initializeParentHierarchy( tests );
|
||||
}
|
||||
|
||||
algorithm = ifNotNullElse( algorithm, new NNSupplier<Integer>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Integer get() {
|
||||
return checkNotNull( parentCase.algorithm );
|
||||
}
|
||||
} );
|
||||
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.fullName );
|
||||
}
|
||||
} );
|
||||
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.masterPassword );
|
||||
}
|
||||
} );
|
||||
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.keyID );
|
||||
}
|
||||
} );
|
||||
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.siteName );
|
||||
}
|
||||
} );
|
||||
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<UnsignedInteger>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public UnsignedInteger get() {
|
||||
return checkNotNull( parentCase.siteCounter );
|
||||
}
|
||||
} );
|
||||
resultType = ifNotNullElse( resultType, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.resultType );
|
||||
}
|
||||
} );
|
||||
keyPurpose = ifNotNullElse( keyPurpose, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.keyPurpose );
|
||||
}
|
||||
} );
|
||||
keyContext = ifNotNullElse( keyContext, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return (parentCase == null)? "": checkNotNull( parentCase.keyContext );
|
||||
}
|
||||
} );
|
||||
result = ifNotNullElse( result, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return (parentCase == null)? "": checkNotNull( parentCase.result );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Case getParentCase() {
|
||||
return parentCase;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPMasterKey.Version getAlgorithm() {
|
||||
return MPMasterKey.Version.fromInt( checkNotNull( algorithm ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
return checkNotNull( fullName );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getMasterPassword() {
|
||||
return checkNotNull( masterPassword );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getKeyID() {
|
||||
return checkNotNull( keyID );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSiteName() {
|
||||
return checkNotNull( siteName );
|
||||
}
|
||||
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return ifNotNullElse( siteCounter, UnsignedInteger.valueOf( 1 ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPResultType getResultType() {
|
||||
return MPResultType.forName( checkNotNull( resultType ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPKeyPurpose getKeyPurpose() {
|
||||
return MPKeyPurpose.forName( checkNotNull( keyPurpose ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getKeyContext() {
|
||||
return checkNotNull( keyContext );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getResult() {
|
||||
return checkNotNull( result );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
../../../../../mpw_tests.xml
|
@ -1,145 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Random;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
||||
public class MPMasterKeyTest {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPMasterKeyTest.class );
|
||||
|
||||
private MPTestSuite testSuite;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp()
|
||||
throws Exception {
|
||||
|
||||
testSuite = new MPTestSuite();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterKey()
|
||||
throws Exception {
|
||||
|
||||
testSuite.forEach( "testMasterKey", new MPTestSuite.TestCase() {
|
||||
@Override
|
||||
public boolean run(final MPTests.Case testCase)
|
||||
throws Exception {
|
||||
char[] masterPassword = testCase.getMasterPassword().toCharArray();
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
||||
|
||||
// Test key
|
||||
assertEquals(
|
||||
CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
|
||||
testCase.getKeyID(),
|
||||
"[testMasterKey] keyID mismatch: " + testCase );
|
||||
|
||||
// Test invalidation
|
||||
masterKey.invalidate();
|
||||
try {
|
||||
masterKey.getKeyID( testCase.getAlgorithm() );
|
||||
fail( "[testMasterKey] invalidate ineffective: " + testCase );
|
||||
}
|
||||
catch (final MPInvalidatedException ignored) {
|
||||
}
|
||||
assertNotEquals(
|
||||
masterPassword,
|
||||
testCase.getMasterPassword().toCharArray(),
|
||||
"[testMasterKey] masterPassword not wiped: " + testCase );
|
||||
|
||||
return true;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSiteResult()
|
||||
throws Exception {
|
||||
|
||||
testSuite.forEach( "testSiteResult", new MPTestSuite.TestCase() {
|
||||
@Override
|
||||
public boolean run(final MPTests.Case testCase)
|
||||
throws Exception {
|
||||
char[] masterPassword = testCase.getMasterPassword().toCharArray();
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
||||
|
||||
// Test site result
|
||||
assertEquals(
|
||||
masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), testCase.getResultType(),
|
||||
null, testCase.getAlgorithm() ),
|
||||
testCase.getResult(),
|
||||
"[testSiteResult] result mismatch: " + testCase );
|
||||
|
||||
return true;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSiteState()
|
||||
throws Exception {
|
||||
|
||||
MPTests.Case testCase = testSuite.getTests().getDefaultCase();
|
||||
char[] masterPassword = testCase.getMasterPassword().toCharArray();
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
||||
|
||||
String password = randomString( 8 );
|
||||
for (final MPMasterKey.Version version : MPMasterKey.Version.values()) {
|
||||
MPResultType resultType = MPResultType.StoredPersonal;
|
||||
|
||||
// Test site state
|
||||
String state = masterKey.siteState( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), resultType, password, version );
|
||||
String result = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), resultType, state, version );
|
||||
|
||||
assertEquals(
|
||||
result,
|
||||
password,
|
||||
"[testSiteState] state mismatch: " + testCase );
|
||||
}
|
||||
}
|
||||
|
||||
public static String randomString(int length) {
|
||||
Random random = new Random();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
while (length > 0) {
|
||||
int codePoint = random.nextInt( Character.MAX_CODE_POINT - Character.MIN_CODE_POINT ) + Character.MIN_CODE_POINT;
|
||||
if (!Character.isDefined( codePoint ) || (Character.getType( codePoint ) == Character.PRIVATE_USE) || Character.isSurrogate(
|
||||
(char) codePoint ))
|
||||
continue;
|
||||
|
||||
builder.appendCodePoint( codePoint );
|
||||
length--;
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<configuration scan="false">
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="${mp.log.level:-TRACE}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
@ -1,279 +0,0 @@
|
||||
<tests>
|
||||
<!-- Default values for all parameters. -->
|
||||
<case id="default">
|
||||
<algorithm>-1</algorithm>
|
||||
<fullName>Robert Lee Mitchell</fullName>
|
||||
<masterPassword>banana colored duckling</masterPassword>
|
||||
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
||||
<siteName>masterpasswordapp.com</siteName>
|
||||
<siteCounter>1</siteCounter>
|
||||
<resultType>Long</resultType>
|
||||
<keyPurpose>Authentication</keyPurpose>
|
||||
<result><!-- abstract --></result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 3 -->
|
||||
<case id="v3" parent="default">
|
||||
<algorithm>3</algorithm>
|
||||
<result>Jejr5[RepuSosp</result>
|
||||
</case>
|
||||
<case id="v3_mb_fullName" parent="v3">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
|
||||
<result>NopaDajh8=Fene</result>
|
||||
</case>
|
||||
<case id="v3_mb_masterPassword" parent="v3">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>QesuHirv5-Xepl</result>
|
||||
</case>
|
||||
<case id="v3_mb_siteName" parent="v3">
|
||||
<siteName>⛄</siteName>
|
||||
<result>LiheCuwhSerz6)</result>
|
||||
</case>
|
||||
<case id="v3_loginName" parent="v3">
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>Name</resultType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v3_securityAnswer" parent="v3">
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>Phrase</resultType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
|
||||
<keyContext>question</keyContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v3_type_maximum" parent="v3">
|
||||
<resultType>Maximum</resultType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v3_type_medium" parent="v3">
|
||||
<resultType>Medium</resultType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v3_type_basic" parent="v3">
|
||||
<resultType>Basic</resultType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v3_type_short" parent="v3">
|
||||
<resultType>Short</resultType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v3_type_pin" parent="v3">
|
||||
<resultType>PIN</resultType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v3_type_name" parent="v3">
|
||||
<resultType>Name</resultType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v3_type_phrase" parent="v3">
|
||||
<resultType>Phrase</resultType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v3_counter_ceiling" parent="v3">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>XambHoqo6[Peni</result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 2 -->
|
||||
<case id="v2" parent="default">
|
||||
<algorithm>2</algorithm>
|
||||
<result>Jejr5[RepuSosp</result>
|
||||
</case>
|
||||
<case id="v2_mb_fullName" parent="v2">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>4D5851D0B093D65DE0CF13D94877270468C0B65A6E42CA50D393AC9B99C457B5</keyID>
|
||||
<result>WaqoGuho2[Xaxw</result>
|
||||
</case>
|
||||
<case id="v2_mb_masterPassword" parent="v2">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>QesuHirv5-Xepl</result>
|
||||
</case>
|
||||
<case id="v2_mb_siteName" parent="v2">
|
||||
<siteName>⛄</siteName>
|
||||
<result>LiheCuwhSerz6)</result>
|
||||
</case>
|
||||
<case id="v2_loginName" parent="v2">
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>Name</resultType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v2_securityAnswer" parent="v2">
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>Phrase</resultType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
|
||||
<keyContext>question</keyContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v2_type_maximum" parent="v2">
|
||||
<resultType>Maximum</resultType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v2_type_medium" parent="v2">
|
||||
<resultType>Medium</resultType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v2_type_basic" parent="v2">
|
||||
<resultType>Basic</resultType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v2_type_short" parent="v2">
|
||||
<resultType>Short</resultType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v2_type_pin" parent="v2">
|
||||
<resultType>PIN</resultType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v2_type_name" parent="v2">
|
||||
<resultType>Name</resultType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v2_type_phrase" parent="v2">
|
||||
<resultType>Phrase</resultType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v2_counter_ceiling" parent="v2">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>XambHoqo6[Peni</result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 1 -->
|
||||
<case id="v1" parent="default">
|
||||
<algorithm>1</algorithm>
|
||||
<result>Jejr5[RepuSosp</result>
|
||||
</case>
|
||||
<case id="v1_mb_fullName" parent="v1">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>4D5851D0B093D65DE0CF13D94877270468C0B65A6E42CA50D393AC9B99C457B5</keyID>
|
||||
<result>WaqoGuho2[Xaxw</result>
|
||||
</case>
|
||||
<case id="v1_mb_masterPassword" parent="v1">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>QesuHirv5-Xepl</result>
|
||||
</case>
|
||||
<case id="v1_mb_siteName" parent="v1">
|
||||
<siteName>⛄</siteName>
|
||||
<result>WawiYarp2@Kodh</result>
|
||||
</case>
|
||||
<case id="v1_loginName" parent="v1">
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>Name</resultType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v1_securityAnswer" parent="v1">
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>Phrase</resultType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
|
||||
<keyContext>question</keyContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v1_type_maximum" parent="v1">
|
||||
<resultType>Maximum</resultType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v1_type_medium" parent="v1">
|
||||
<resultType>Medium</resultType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v1_type_basic" parent="v1">
|
||||
<resultType>Basic</resultType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v1_type_short" parent="v1">
|
||||
<resultType>Short</resultType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v1_type_pin" parent="v1">
|
||||
<resultType>PIN</resultType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v1_type_name" parent="v1">
|
||||
<resultType>Name</resultType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v1_type_phrase" parent="v1">
|
||||
<resultType>Phrase</resultType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v1_counter_ceiling" parent="v1">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>XambHoqo6[Peni</result>
|
||||
</case>
|
||||
|
||||
<!-- Algorithm 0 -->
|
||||
<case id="v0" parent="default">
|
||||
<algorithm>0</algorithm>
|
||||
<result>Feji5@ReduWosh</result>
|
||||
</case>
|
||||
<case id="v0_mb_fullName" parent="v0">
|
||||
<fullName>⛄</fullName>
|
||||
<keyID>4D5851D0B093D65DE0CF13D94877270468C0B65A6E42CA50D393AC9B99C457B5</keyID>
|
||||
<result>HajrYudo7@Mamh</result>
|
||||
</case>
|
||||
<case id="v0_mb_masterPassword" parent="v0">
|
||||
<masterPassword>⛄</masterPassword>
|
||||
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
|
||||
<result>MewmDini0]Meho</result>
|
||||
</case>
|
||||
<case id="v0_mb_siteName" parent="v0">
|
||||
<siteName>⛄</siteName>
|
||||
<result>HahiVana2@Nole</result>
|
||||
</case>
|
||||
<case id="v0_loginName" parent="v0">
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>Name</resultType>
|
||||
<result>lozwajave</result>
|
||||
</case>
|
||||
<case id="v0_securityAnswer" parent="v0">
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>Phrase</resultType>
|
||||
<result>miy lirfijoja dubu</result>
|
||||
</case>
|
||||
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
|
||||
<keyContext>question</keyContext>
|
||||
<result>movm bex gevrica jaf</result>
|
||||
</case>
|
||||
<case id="v0_type_maximum" parent="v0">
|
||||
<resultType>Maximum</resultType>
|
||||
<result>w1!3bA3icmRAc)SS@lwl</result>
|
||||
</case>
|
||||
<case id="v0_type_medium" parent="v0">
|
||||
<resultType>Medium</resultType>
|
||||
<result>Fej7]Jug</result>
|
||||
</case>
|
||||
<case id="v0_type_basic" parent="v0">
|
||||
<resultType>Basic</resultType>
|
||||
<result>wvH7irC1</result>
|
||||
</case>
|
||||
<case id="v0_type_short" parent="v0">
|
||||
<resultType>Short</resultType>
|
||||
<result>Fej7</result>
|
||||
</case>
|
||||
<case id="v0_type_pin" parent="v0">
|
||||
<resultType>PIN</resultType>
|
||||
<result>2117</result>
|
||||
</case>
|
||||
<case id="v0_type_name" parent="v0">
|
||||
<resultType>Name</resultType>
|
||||
<result>fejrajugo</result>
|
||||
</case>
|
||||
<case id="v0_type_phrase" parent="v0">
|
||||
<resultType>Phrase</resultType>
|
||||
<result>fejr jug gabsibu bax</result>
|
||||
</case>
|
||||
<case id="v0_counter_ceiling" parent="v0">
|
||||
<siteCounter>4294967295</siteCounter>
|
||||
<result>QateDojh1@Hecn</result>
|
||||
</case>
|
||||
</tests>
|
||||
|
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectCodeStyleSettingsManager">
|
||||
<option name="PER_PROJECT_SETTINGS">
|
||||
<value />
|
||||
</option>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Lhunath" />
|
||||
</component>
|
||||
</project>
|
@ -1,7 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="keyword" value="Copyright|License|WARRANTY" />
|
||||
<option name="myName" value="GPLv3" />
|
||||
<option name="notice" value="This file is part of &#36;project.name. Copyright (c) &#36;today.year. &#36;project.name is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. &#36;project.name is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You can find a copy of the GNU General Public License in the LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>." />
|
||||
</copyright>
|
||||
</component>
|
@ -1,13 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings>
|
||||
<module2copyright>
|
||||
<element module="masterpassword" copyright="Master Password" />
|
||||
</module2copyright>
|
||||
<LanguageOptions name="__TEMPLATE__">
|
||||
<option name="block" value="false" />
|
||||
<option name="separateBefore" value="true" />
|
||||
<option name="separateAfter" value="true" />
|
||||
<option name="filler" value="=" />
|
||||
</LanguageOptions>
|
||||
</settings>
|
||||
</component>
|
@ -1,9 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="projectProfile" value="Lhunath" />
|
||||
<option name="useProjectProfile" value="false" />
|
||||
<option name="PROJECT_PROFILE" value="Lhunath" />
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/../../opal/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="javax.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="javax.annotation.Nonnull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/classes" />
|
||||
</component>
|
||||
<component name="ThriftCompiler">
|
||||
<compilers />
|
||||
</component>
|
||||
</project>
|
@ -1,28 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Android" type="AndroidRunConfigurationType" factoryName="Android App">
|
||||
<module name="android" />
|
||||
<option name="DEPLOY" value="true" />
|
||||
<option name="ARTIFACT_NAME" value="" />
|
||||
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||
<option name="MODE" value="default_activity" />
|
||||
<option name="PREFERRED_AVD" value="" />
|
||||
<option name="CLEAR_LOGCAT" value="false" />
|
||||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
|
||||
<option name="FORCE_STOP_RUNNING_APP" value="true" />
|
||||
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
|
||||
<option name="USE_LAST_SELECTED_DEVICE" value="false" />
|
||||
<option name="PREFERRED_AVD" value="" />
|
||||
<option name="DEBUGGER_TYPE" value="Java" />
|
||||
<Java />
|
||||
<Profilers>
|
||||
<option name="ENABLE_ADVANCED_PROFILING" value="false" />
|
||||
<option name="SUPPORT_LIB_ENABLED" value="true" />
|
||||
<option name="INSTRUMENTATION_ENABLED" value="true" />
|
||||
</Profilers>
|
||||
<option name="DEEP_LINK" value="" />
|
||||
<option name="ACTIVITY_CLASS" value="" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
@ -1,16 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="GUI" type="Application" factoryName="Application" show_console_on_std_err="true">
|
||||
<option name="MAIN_CLASS_NAME" value="com.lyndir.masterpassword.gui.GUI" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="ENABLE_SWING_INSPECTOR" value="false" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<module name="masterpassword-gui" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
@ -1,29 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Tests" type="TestNG" factoryName="TestNG" show_console_on_std_err="true">
|
||||
<module name="" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="SUITE_NAME" value="" />
|
||||
<option name="PACKAGE_NAME" value="com.lyndir.masterpassword" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="GROUP_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="PACKAGE" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../core/java/tests" />
|
||||
<option name="OUTPUT_DIRECTORY" value="" />
|
||||
<option name="ANNOTATION_TYPE" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="wholeProject" />
|
||||
</option>
|
||||
<option name="USE_DEFAULT_REPORTERS" value="false" />
|
||||
<option name="PROPERTIES_FILE" value="" />
|
||||
<envs />
|
||||
<properties />
|
||||
<listeners />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
@ -1,3 +0,0 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="masterpassword" pattern="com.lyndir.masterpassword..*" />
|
||||
</component>
|
@ -1,34 +0,0 @@
|
||||
allprojects {
|
||||
//apply plugin: 'findbugs'
|
||||
|
||||
group = 'com.lyndir.masterpassword'
|
||||
version = 'GIT-SNAPSHOT'
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = '1.7'
|
||||
targetCompatibility = '1.7'
|
||||
}
|
||||
tasks.withType(FindBugs) {
|
||||
reports {
|
||||
xml.enabled false
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath group: 'com.android.tools.build', name: 'gradle', version: '2.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'http://maven.lyndir.com' }
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<root>
|
||||
<item name='com.google.common.base.Preconditions T checkNotNull(T, java.lang.Object) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NonNls' />
|
||||
</item>
|
||||
</root>
|
@ -1,5 +0,0 @@
|
||||
<root>
|
||||
<item name='com.google.common.io.Resources java.net.URL getResource(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NonNls' />
|
||||
</item>
|
||||
</root>
|
@ -1,5 +0,0 @@
|
||||
<root>
|
||||
<item name='org.testng.Assert void assertEquals(java.lang.String, java.lang.String, java.lang.String) 2'>
|
||||
<annotation name='org.jetbrains.annotations.NonNls' />
|
||||
</item>
|
||||
</root>
|
@ -1 +0,0 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
BIN
gradle/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@ -1,6 +0,0 @@
|
||||
#Sun Mar 26 09:11:08 EDT 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
172
gradle/gradlew
vendored
172
gradle/gradlew
vendored
@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save ( ) {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
84
gradle/gradlew.bat
vendored
84
gradle/gradlew.bat
vendored
@ -1,84 +0,0 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
@ -1,31 +0,0 @@
|
||||
class Mpw < Formula
|
||||
homepage "http://masterpasswordapp.com"
|
||||
url "https://ssl.masterpasswordapp.com/mpw-2.1-cli4-0-gf6b2287.tar.gz"
|
||||
sha1 "036b3d8f4bd6f0676ae16e7e9c3de65f6030874f"
|
||||
version "2.1-cli4"
|
||||
|
||||
depends_on "automake" => :build
|
||||
depends_on "autoconf" => :build
|
||||
depends_on "openssl"
|
||||
|
||||
resource "libscrypt" do
|
||||
url "http://masterpasswordapp.com/libscrypt-b12b554.tar.gz"
|
||||
sha1 "ee871e0f93a786c4e3622561f34565337cfdb815"
|
||||
end
|
||||
|
||||
def install
|
||||
resource("libscrypt").stage buildpath/"lib/scrypt"
|
||||
touch "lib/scrypt/.unpacked"
|
||||
|
||||
ENV["targets"] = "mpw mpw-tests"
|
||||
system "./build"
|
||||
system "./mpw-tests"
|
||||
|
||||
bin.install "mpw"
|
||||
end
|
||||
|
||||
test do
|
||||
assert_equal "Jejr5[RepuSosp",
|
||||
shell_output("mpw -u 'Robert Lee Mitchell' -P 'banana colored duckling' masterpasswordapp.com").strip
|
||||
end
|
||||
end
|
@ -1,60 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath</groupId>
|
||||
<artifactId>lyndir</artifactId>
|
||||
<version>1.22</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password</name>
|
||||
<description>A Java implementation of the Master Password algorithm.</description>
|
||||
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>masterpassword-tests</module>
|
||||
<module>masterpassword-algorithm</module>
|
||||
<module>masterpassword-model</module>
|
||||
<module>masterpassword-gui</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<modules>
|
||||
<module>masterpassword-android</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>mod:android</id>
|
||||
<modules>
|
||||
<module>masterpassword-android</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<!-- REMOTE ARTIFACT REPOSITORIES -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>lyndir</id>
|
||||
<name>Lyndir Repository</name>
|
||||
<url>http://maven.lyndir.com</url>
|
||||
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
<updatePolicy>never</updatePolicy>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
<updatePolicy>never</updatePolicy>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
@ -1,26 +0,0 @@
|
||||
rootProject.name = 'masterpassword'
|
||||
|
||||
def local = new Properties();
|
||||
try {
|
||||
local.load(file('local.properties').newDataInputStream())
|
||||
} catch (FileNotFoundException ignored) {
|
||||
}
|
||||
|
||||
include 'masterpassword-algorithm'
|
||||
project(':masterpassword-algorithm').projectDir = new File( '../core/java/algorithm' )
|
||||
|
||||
include 'masterpassword-model'
|
||||
project(':masterpassword-model').projectDir = new File( '../core/java/model' )
|
||||
|
||||
include 'masterpassword-tests'
|
||||
project(':masterpassword-tests').projectDir = new File( '../core/java/tests' )
|
||||
|
||||
include 'masterpassword-gui'
|
||||
project(':masterpassword-gui').projectDir = new File( '../platform-independent/gui-java' )
|
||||
|
||||
if (local.containsKey('sdk.dir')) {
|
||||
include 'masterpassword-android'
|
||||
project(':masterpassword-android').projectDir = new File( '../platform-android' )
|
||||
} else {
|
||||
logger.warn( "Skipping masterpassword-android since sdk.dir is not defined in local.properties." )
|
||||
}
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user