mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
441 lines
14 KiB
HTML
441 lines
14 KiB
HTML
<!doctype html>
|
|
<html>
|
|
|
|
<!--
|
|
|
|
!!! WARNING !!!
|
|
|
|
This file was auto-generated from readme/spec.md and any manual change
|
|
made to it will be overwritten. To make a change to this file please modify
|
|
the source Markdown file:
|
|
|
|
https://github.com/laurent22/joplin/blob/master/readme/spec.md
|
|
|
|
-->
|
|
|
|
<head>
|
|
<title>Specifications | Joplin</title>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="https://joplinapp.org/css/bootstrap.min.css">
|
|
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
|
<link rel="stylesheet" href="https://joplinapp.org/css/fontawesome-all.min.css">
|
|
<script src="https://joplinapp.org/js/jquery-3.2.1.slim.min.js"></script>
|
|
<style>
|
|
body {
|
|
background-color: #F1F1F1;
|
|
color: #333333;
|
|
}
|
|
table {
|
|
margin-bottom: 1em;
|
|
}
|
|
td, th {
|
|
padding: .8em;
|
|
border: 1px solid #ccc;
|
|
}
|
|
|
|
.page-markdown table pre,
|
|
.page-markdown table blockquote {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.page-markdown table pre,
|
|
.page-markdown table blockquote {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.page-markdown table pre {
|
|
background-color: rgba(0,0,0,0);
|
|
border: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
h1, h2 {
|
|
border-bottom: 1px solid #eaecef;
|
|
padding-bottom: 0.3em;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
font-weight: 600;
|
|
font-size: 2em;
|
|
margin-bottom: 16px;
|
|
}
|
|
h2 {
|
|
font-size: 1.6em;
|
|
}
|
|
h3 {
|
|
font-size: 1.3em;
|
|
}
|
|
code {
|
|
color: black;
|
|
background-color: #eee;
|
|
border: 1px solid #ccc;
|
|
font-size: .85em;
|
|
}
|
|
pre code {
|
|
border: none;
|
|
}
|
|
pre {
|
|
font-size: .85em;
|
|
}
|
|
blockquote {
|
|
font-size: 1em;
|
|
color: #555;
|
|
};
|
|
#toc ul {
|
|
margin-bottom: 10px;
|
|
}
|
|
#toc {
|
|
padding-bottom: 1em;
|
|
}
|
|
.title-icon {
|
|
height: 2em;
|
|
}
|
|
.sub-title {
|
|
font-weight: bold;
|
|
font-size: 1.5em;
|
|
}
|
|
.container {
|
|
background-color: white;
|
|
padding: 0;
|
|
box-shadow: 0 10px 20px #888888;
|
|
}
|
|
table.screenshots {
|
|
margin-top: 2em;
|
|
margin-bottom: 2em;
|
|
}
|
|
table.screenshots th {
|
|
height: 3em;
|
|
text-align: center;
|
|
}
|
|
table.screenshots th,
|
|
table.screenshots td {
|
|
border: 1px solid #C2C2C2;
|
|
}
|
|
img[align="left"] {
|
|
margin-right: 10px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.mobile-screenshot {
|
|
height: 40em;
|
|
padding: 1em;
|
|
}
|
|
.cli-screenshot-wrapper {
|
|
background-color: black;
|
|
vertical-align: top;
|
|
padding: 1em 2em 1em 1em;
|
|
}
|
|
.cli-screenshot {
|
|
font-family: "Monaco", "Inconsolata", "CONSOLAS", "Deja Vu Sans Mono", "Droid Sans Mono", "Andale Mono", monospace;
|
|
background-color: black;
|
|
color: white;
|
|
border: none;
|
|
}
|
|
.cli-screenshot .prompt {
|
|
color: #48C2F0;
|
|
}
|
|
.top-screenshot {
|
|
margin-top: 2em;
|
|
text-align: center;
|
|
}
|
|
.header {
|
|
position: relative;
|
|
padding-left: 2em;
|
|
padding-right: 2em;
|
|
padding-top: 1em;
|
|
padding-bottom: 1em;
|
|
color: white;
|
|
background-color: #2B2B3D;
|
|
}
|
|
.header a h1 {
|
|
color: white;
|
|
}
|
|
.content {
|
|
padding-left: 2em;
|
|
padding-right: 2em;
|
|
padding-bottom: 2em;
|
|
padding-top: 2em;
|
|
}
|
|
.forkme {
|
|
position: absolute;
|
|
right: 0;
|
|
top:0;
|
|
}
|
|
.nav-wrapper {
|
|
position: relative;
|
|
width: inherit;
|
|
}
|
|
.nav {
|
|
background-color: black;
|
|
display: table;
|
|
width: inherit;
|
|
}
|
|
.nav.sticky {
|
|
position:fixed;
|
|
top: 0;
|
|
width: inherit;
|
|
box-shadow: 0 0 10px #000000;
|
|
}
|
|
.nav a {
|
|
color: white;
|
|
display: inline-block;
|
|
padding: .6em .9em .6em .9em;
|
|
}
|
|
.nav ul {
|
|
padding-left: 2em;
|
|
margin-bottom: 0;
|
|
display: table-cell;
|
|
min-width: 250px;
|
|
}
|
|
.nav ul li {
|
|
display: inline-block;
|
|
padding: 0;
|
|
}
|
|
.nav li.selected {
|
|
background-color: #222;
|
|
font-weight: bold;
|
|
}
|
|
.nav-right {
|
|
display: table-cell;
|
|
width: 100%;
|
|
text-align: right;
|
|
vertical-align: middle;
|
|
line-height: 0;
|
|
}
|
|
.nav-right .share-btn {
|
|
display: none;
|
|
}
|
|
.nav-right .small-share-btn {
|
|
display: none;
|
|
}
|
|
.footer {
|
|
padding-top: 1em;
|
|
border-top: 1px solid #d4d4d4;
|
|
margin-top: 2em;
|
|
color: gray;
|
|
font-size: .9em;
|
|
}
|
|
a.heading-anchor {
|
|
display: inline-block;
|
|
opacity: 0;
|
|
width: 1.3em;
|
|
font-size: 0.7em;
|
|
margin-left: 0.4em;
|
|
line-height: 1em;
|
|
text-decoration: none;
|
|
transition: opacity 0.3s;
|
|
}
|
|
a.heading-anchor:hover,
|
|
h1:hover a.heading-anchor,
|
|
h2:hover a.heading-anchor,
|
|
h3:hover a.heading-anchor,
|
|
h4:hover a.heading-anchor,
|
|
h5:hover a.heading-anchor,
|
|
h6:hover a.heading-anchor {
|
|
opacity: 1;
|
|
}
|
|
@media all and (min-width: 400px) {
|
|
.nav-right .share-btn {
|
|
display: inline-block;
|
|
}
|
|
.nav-right .small-share-btn {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="container page-spec">
|
|
|
|
<div class="header">
|
|
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="https://joplinapp.org/images/ForkMe.png"/></a>
|
|
<a href="https://joplinapp.org"><h1 id="joplin"><img class="title-icon" src="https://joplinapp.org/images/Icon512.png">oplin</h1></a>
|
|
<p class="sub-title">An open source note taking and to-do application with synchronisation capabilities.</p>
|
|
</div>
|
|
|
|
<div class="nav-wrapper">
|
|
<div class="nav">
|
|
<ul>
|
|
<li class=""><a href="https://joplinapp.org/" title="Home"><i class="fa fa-home"></i></a></li>
|
|
<li><a href="https://discourse.joplinapp.org" title="Forum">Forum</a></li>
|
|
<li><a class="help" href="#" title="Menu">Menu</a></li>
|
|
</ul>
|
|
<div class="nav-right">
|
|
<!--
|
|
<iframe class="share-btn" src="https://www.facebook.com/plugins/share_button.php?href=http%3A%2F%2Fjoplinapp.org&layout=button&size=small&mobile_iframe=true&width=60&height=20&appId" width="60" height="20" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>
|
|
<iframe class="share-btn" src="https://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fjoplinapp.org" width="62" height="20" title="Tweet" style="border: 0; overflow: hidden;"></iframe>
|
|
-->
|
|
<iframe class="share-btn share-btn-github" src="https://ghbtns.com/github-btn.html?user=laurent22&repo=joplin&type=star&count=true" frameborder="0" scrolling="0" width="100px" height="20px"></iframe>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<div id="toc"><ul>
|
|
<li>
|
|
<p>Applications</p>
|
|
<ul>
|
|
<li><a href="https://joplinapp.org/desktop/">Desktop application</a></li>
|
|
<li><a href="https://joplinapp.org/mobile/">Mobile applications</a></li>
|
|
<li><a href="https://joplinapp.org/terminal/">Terminal application</a></li>
|
|
<li><a href="https://joplinapp.org/clipper/">Web Clipper</a></li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<p>Support</p>
|
|
<ul>
|
|
<li><a href="https://discourse.joplinapp.org">Joplin Forum</a></li>
|
|
<li><a href="https://joplinapp.org/markdown/">Markdown Guide</a></li>
|
|
<li><a href="https://joplinapp.org/e2ee/">How to enable end-to-end encryption</a></li>
|
|
<li><a href="https://joplinapp.org/spec/">End-to-end encryption spec</a></li>
|
|
<li><a href="https://joplinapp.org/debugging/">How to enable debug mode</a></li>
|
|
<li><a href="https://joplinapp.org/api/">API documentation</a></li>
|
|
<li><a href="https://joplinapp.org/faq/">FAQ</a></li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<p>About</p>
|
|
<ul>
|
|
<li><a href="https://joplinapp.org/changelog/">Changelog (Desktop App)</a></li>
|
|
<li><a href="https://joplinapp.org/changelog_cli/">Changelog (CLI App)</a></li>
|
|
<li><a href="https://joplinapp.org/stats/">Stats</a></li>
|
|
<li><a href="https://joplinapp.org/donate/">Donate</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<h1>Encryption<a name="encryption" href="#encryption" class="heading-anchor">🔗</a></h1>
|
|
<p>Encrypted data is encoded to ASCII because encryption/decryption functions in React Native can only deal with strings. So for compatibility with all the apps we need to use the lowest common denominator.</p>
|
|
<h2>Encrypted data format<a name="encrypted-data-format" href="#encrypted-data-format" class="heading-anchor">🔗</a></h2>
|
|
<h3>Header<a name="header" href="#header" class="heading-anchor">🔗</a></h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Size</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Identifier</td>
|
|
<td>3 chars ("JED")</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Version number</td>
|
|
<td>2 chars (Hexa string)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p>This is followed by the encryption metadata:</p>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Size</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Length</td>
|
|
<td>6 chars (Hexa string)</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Encryption method</td>
|
|
<td>2 chars (Hexa string)</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Master key ID</td>
|
|
<td>32 chars (Hexa string)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p>See <code>lib/services/EncryptionService.js</code> for the list of available encryption methods.</p>
|
|
<h3>Data chunk<a name="data-chunk" href="#data-chunk" class="heading-anchor">🔗</a></h3>
|
|
<p>The data is encoded in one or more chunks for performance reasons. That way it is possible to take a block of data from one file and encrypt it to another block in another file. Encrypting/decrypting the whole file in one go would not work (on mobile especially).</p>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Size</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Length</td>
|
|
<td>6 chars (Hexa string)</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Data</td>
|
|
<td>("Length" bytes) (ASCII)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<h2>Master Keys<a name="master-keys" href="#master-keys" class="heading-anchor">🔗</a></h2>
|
|
<p>The master keys are used to encrypt and decrypt data. They can be generated from the Encryption Service and are saved to the database. They are themselves encrypted via a user password using a <a href="https://github.com/laurent22/joplin/blob/fb6dee32ac035b00153106273135fb16be4b4fa5/ReactNativeClient/lib/services/EncryptionService.js#L263">strong encryption method</a>.</p>
|
|
<p>These encrypted master keys are transmitted with the sync data so that they can be available to each client. Each client will need to supply the user password to decrypt each key.</p>
|
|
<p>The application supports multiple master keys in order to handle cases where one offline client starts encrypting notes, then another offline client starts encrypting notes too, and later both sync. Both master keys will have to be decrypted separately with the user password.</p>
|
|
<p>Only one master key can be active for encryption purposes. For decryption, the algorithm will check the Master Key ID in the header, then check if it's available to the current app and, if so, use this for decryption.</p>
|
|
<h2>Encryption Service<a name="encryption-service" href="#encryption-service" class="heading-anchor">🔗</a></h2>
|
|
<p>The applications make use of the <code>EncryptionService</code> class to handle encryption and decryption. Before it can be used, a least one master key must be loaded into it and be marked as "active".</p>
|
|
<h2>Encryption workflow<a name="encryption-workflow" href="#encryption-workflow" class="heading-anchor">🔗</a></h2>
|
|
<p>Items are encrypted only during synchronisation, when they are serialised (via <code>BaseItem.serializeForSync</code>), so before being sent to the sync target.</p>
|
|
<p>They are decrypted by DecryptionWorker in the background.</p>
|
|
<p>The apps handle displaying both decrypted and encrypted items, so that user is aware that these items are there even if not yet decrypted. Encrypted items are mostly read-only to the user, except that they can be deleted.</p>
|
|
<h2>Enabling and disabling encryption<a name="enabling-and-disabling-encryption" href="#enabling-and-disabling-encryption" class="heading-anchor">🔗</a></h2>
|
|
<p>Enabling/disabling E2EE while two clients are in sync might have an unintuitive behaviour (although that behaviour might be correct), so below some scenarios are explained:</p>
|
|
<ul>
|
|
<li>
|
|
<p>If client 1 enables E2EE, all items will be synced to target and will appear encrypted on target. Although all items have been re-uploaded to the target, their timestamps did <em>not</em> change (because the item data itself has not changed, only its representation). Because of this, client 2 will not re-download the items - it does not need to do so anyway since it has already the item data.</p>
|
|
</li>
|
|
<li>
|
|
<p>When a client sync and download a master key for the first time, encryption will be automatically enabled (user will need to supply the master key password). In that case, all items that are not encrypted will be re-synced. Uploading only non-encrypted items is an optimisation since if an item is already encrypted locally it means it's encrypted on target too.</p>
|
|
</li>
|
|
<li>
|
|
<p>If both clients are in sync with E2EE enabled: if client 1 disable E2EE, it's going to re-upload all the items unencrypted. Client 2 again will not re-download the items for the same reason as above (data did not change, only representation). Note that user <em>must</em> manually disable E2EE on all clients otherwise some will continue to upload encrypted items. Since synchronisation is stateless, clients do not know whether other clients use E2EE or not so this step has to be manual.</p>
|
|
</li>
|
|
<li>
|
|
<p>Although messy, Joplin supports having some clients send encrypted items and others unencrypted ones. The situation gets resolved once all the clients have the same E2EE settings.</p>
|
|
</li>
|
|
<li>
|
|
<p>Currently, there is no way to delete encryption keys if you do not need them anymore or if you disabled the encryption completely. You will get a persistant notification to provide a Master Key password on a new device, even if encryption is disabled. Entering the Master Key(s) password and still having the encryption disabled will get rid of the notification. See <a href="https://discourse.joplinapp.org/t/delete-e2ee-master-keys/906">Delete E2EE Master Keys</a> for more info.</p>
|
|
</li>
|
|
</ul>
|
|
|
|
<script>
|
|
function stickyHeader() {
|
|
return; // Disabled
|
|
|
|
if ($(window).scrollTop() > 179) {
|
|
$('.nav').addClass('sticky');
|
|
} else {
|
|
$('.nav').removeClass('sticky');
|
|
}
|
|
}
|
|
|
|
$('#toc').hide();
|
|
|
|
$('.help').click(function(event) {
|
|
event.preventDefault();
|
|
$('#toc').show();
|
|
});
|
|
|
|
$(window).scroll(function() {
|
|
stickyHeader();
|
|
});
|
|
|
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
|
ga('create', 'UA-103586105-1', 'auto');
|
|
ga('send', 'pageview');
|
|
</script>
|
|
|
|
<div class="footer">
|
|
Copyright (c) 2016-2019 Laurent Cozic
|
|
</div>
|
|
</body>
|
|
</html>
|