Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hugo-site/content/apps/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Anonymous, Sybil-resistant identity certificates. Donate a small amount to
mint a key; apps can verify the certificate without learning who you are. Used
across Freenet apps as a spam- and abuse-resistance primitive.

→ [Get a Ghost Key](/ghostkey/) · [github.com/freenet/ghostkeys](https://github.com/freenet/ghostkeys)
→ [Get a Ghost Key](/ghostkey/) · [Open your vault](/open/#DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N/) · [github.com/freenet/ghostkeys](https://github.com/freenet/ghostkeys)

### freenet-scaffold {#freenet-scaffold}

Expand Down
14 changes: 8 additions & 6 deletions hugo-site/content/build/manual/share-links.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,21 @@ https://freenet.org/open#6FzSeAUKcqJrveKyU8RJgGKc5jRB1Z2juvxXtwTA4Em9/#store=Ab3

## What the page does

`/open` validates the contract id and shows four buttons, in this order:
`/open` validates the contract id and shows these buttons, in this order:

- **Open on this computer** (currently the highlighted, primary button) -- the target on the
visitor's own local peer (`http://127.0.0.1:7509/v1/contract/web/<contract-id>/...`), for anyone
who already has Freenet installed and running.
- **Use in your browser** -- the same target on `try.freenet.org`, a peer we host, for anyone who
wants to look without installing anything.
wants to look without installing anything. Apps that hold keys a visitor should keep on their own
peer, currently just the Ghost Key vault, get a note saying so instead of this button; the list is
`LOCAL_ONLY` in `open-link.html`.
- **Open in Freenet** -- `freenet:<contract-id>/<path>...`, which the visitor's own Freenet opens
on their local peer. The handler ([freenet-core#5726](https://github.com/freenet/freenet-core/issues/5726))
ships in the first release after 0.2.139, so until peers have updated this button is styled and
ordered as a secondary option. The link has no `//`: in `freenet://<contract-id>` the
case-sensitive contract id would be the URL's host, which some desktops lowercase before the
handler sees it. (The handler accepts both forms.)
ships in the first release after 0.2.139, so this button is hidden until that release is out,
and then shown as a secondary option until peers have updated. The link has no `//`: in
`freenet://<contract-id>` the case-sensitive contract id would be the URL's host, which some
desktops lowercase before the handler sees it. (The handler accepts both forms.)
- **Get Freenet** -- the [install guide](/quickstart/).

An invalid or truncated fragment shows a "this link looks broken" message instead of guessing at
Expand Down
15 changes: 10 additions & 5 deletions hugo-site/content/ghostkey/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ hold a scarce, donation-backed identity, without ever learning who you are.
<div class="gk-cta gk-cta-hero">
<a href="/ghostkey/create/" class="funding-donate-button">Get a Ghost Key</a>
<p class="gk-cta-note">$1 minimum. Freenet Project Inc is a 501(c)(3) nonprofit.</p>
<p class="gk-cta-note">Already have one? <a href="/open/#DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N/">Open your vault</a>.</p>
</div>

## The short version
Expand Down Expand Up @@ -206,15 +207,19 @@ new node later.

### Opening your vault

If you have a Freenet node running on this computer, your Ghost Keys are here:
Your Ghost Keys live in the vault on your own Freenet peer:

<div class="gk-cta">
<a href="http://localhost:7509/v1/contract/web/DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N/" class="funding-donate-button">Open your Ghost Key vault</a>
<p class="gk-cta-note">Requires a Freenet node running on this computer. The link will not resolve otherwise.</p>
<a href="/open/#DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N/" class="funding-donate-button">Open your Ghost Key vault</a>
<p class="gk-cta-note">Opens the vault on the Freenet peer running on this computer, or shows you how to install one.</p>
</div>

That address is your own machine, not a website — the vault runs inside your node, and the page is
served locally. Bookmark it if you use Ghost Keys regularly.
The vault runs inside your peer, not on a website, so its address is on your own machine:
[`http://127.0.0.1:7509/v1/contract/web/DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N/`](http://127.0.0.1:7509/v1/contract/web/DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N/).
Bookmark it if you use Ghost Keys regularly.

Keep your Ghost Keys on your own peer rather than on [try.freenet.org](/try/). That hosted peer is
for trying Freenet out, and a key kept there lives on a machine we run instead of yours.

For developers, everything is open source:

Expand Down
6 changes: 3 additions & 3 deletions hugo-site/content/ghostkey/success/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ from one at any time.

**Your vault lives at**
[localhost:7509/v1/contract/web/DLog47hEs…](http://localhost:7509/v1/contract/web/DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N/),
on this computer rather than on the web. Importing opens it for you, but the
address is worth bookmarking — it is how you reach your keys later, and nothing
else links to it.
on this computer rather than on the web. Importing opens it for you. Bookmark
the address, since it is how you reach your keys later, or find it again from
the [Ghost Key page](/ghostkey/#opening-your-vault).

{{< bulma-button href="/ghostkey/" color="#339966" >}}Ghost Key FAQ{{< /bulma-button >}}
7 changes: 4 additions & 3 deletions hugo-site/static/js/donation-success.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,10 @@ function displayCertificate(armoredCertificate, armoredSigningKey) {
const certB64 = urlSafeBase64(armoredCertificate);
const skB64 = urlSafeBase64(armoredSigningKey);
const contractId = 'DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N';
// Use localhost rather than the literal 127.0.0.1: the node binds its
// API on the IPv6 loopback by default, so on Windows "localhost" (::1)
// is reachable while the literal IPv4 address is refused.
// localhost rather than the literal 127.0.0.1 dates from when the node
// served its API on the IPv6 loopback only, so Windows refused the
// IPv4 literal. Nodes serve both since freenet-core#4332; localhost
// still reaches peers older than that.
// If the donor arrived from an app, hand the vault the way back so it
// can offer a one-click return once the key has actually landed.
// Re-validated here because it has been through a Stripe redirect;
Expand Down
52 changes: 47 additions & 5 deletions hugo-site/tests/open-link-vectors.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,23 @@ function runPage(hash) {
getElementById: el,
addEventListener: (ev, fn) => (listeners[ev] = fn),
};
const windowListeners = {};
const window = {
location: { hash },
addEventListener: () => {},
addEventListener: (ev, fn) => (windowListeners[ev] = fn),
};
vm.runInNewContext(match[1], { document, window });
listeners.DOMContentLoaded();
const shown = ["open-link-missing", "open-link-invalid", "open-link-valid"].find(
(id) => el(id).style.display === "",
);
return { shown, el };
const shown = () =>
["open-link-missing", "open-link-invalid", "open-link-valid"].find(
(id) => el(id).style.display === "",
);
const navigate = (newHash) => {
window.location.hash = newHash;
windowListeners.hashchange();
return shown();
};
return { shown: shown(), el, navigate };
}

const { vectors } = JSON.parse(
Expand Down Expand Up @@ -70,6 +77,41 @@ for (const v of vectors) {
console.error(`FAIL ${JSON.stringify(v.raw.slice(0, 80))} (${v.note}): ${problem}`);
}
}

// Local-only apps (the Ghost Key vault): no try.freenet.org button, a note
// saying why instead, and the local button unaffected. Driven through a
// hashchange to an ordinary id too, since both states must be reset.
const VAULT = "DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N";
const OTHER = "6FzSeAUKcqJrveKyU8RJgGKc5jRB1Z2juvxXtwTA4Em9";
{
const check = (cond, what) => {
if (!cond) {
failures++;
console.error(`FAIL local-only: ${what}`);
}
};
const tryHidden = (el) => el("open-link-try-option").style.display === "none";
const noteShown = (el) =>
el("open-link-local-only").style.display === "" &&
el("open-link-local-only").textContent.length > 0;

const { shown, el, navigate } = runPage("#" + VAULT + "/");
check(shown === "open-link-valid", `vault link showed ${shown}`);
check(tryHidden(el), "try option visible for the vault");
check(noteShown(el), "no local-only note for the vault");
check(
el("open-link-local").href === `http://127.0.0.1:7509/v1/contract/web/${VAULT}/`,
`vault local button ${el("open-link-local").href}`,
);

check(navigate("#" + OTHER + "/") === "open-link-valid", "other id not valid");
check(!tryHidden(el), "try option still hidden after leaving the vault");
check(el("open-link-local-only").style.display === "none", "note still shown after leaving the vault");

const fresh = runPage("#" + OTHER + "/");
check(!tryHidden(fresh.el), "try option hidden for an ordinary id");
}

console.log(`${vectors.length} vectors, ${failures} failures`);
if (vectors.length < 40) {
console.error("vector file looks truncated");
Expand Down
36 changes: 32 additions & 4 deletions hugo-site/themes/freenet/layouts/shortcodes/open-link.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,25 @@ <h2>Open this link</h2>
Use this if Freenet is already installed and running on this computer.
</p>
</div>
<div class="open-link-option">
<div id="open-link-try-option" class="open-link-option">
<a id="open-link-try" class="button" href="#" target="_blank" rel="noopener noreferrer"
>Use in your browser</a
>
<p class="open-link-note">No install. Runs on a peer we host, not yours.</p>
</div>
<div class="open-link-option">
<p id="open-link-local-only" class="open-link-note" style="display: none"></p>
<!-- HIDDEN until a Freenet release registers the freenet: link handler
(freenet-core#5726, merged in #5753 after 0.2.139 was cut). No
release has it yet, so the button only produced "no app can open
this link". The JS still fills in its href and the vector test
still checks it. TO RE-ENABLE once that release is out: delete
style="display: none" below, then see the comment above about
making this the primary button once peers have auto-updated. -->
<div id="open-link-scheme-option" class="open-link-option" style="display: none">
<a id="open-link-scheme" class="button" href="#">Open in Freenet</a>
<p class="open-link-note">
Needs a Freenet release newer than 0.2.139 (it registers freenet: links). If nothing
happens, try the options above instead.
Opens it in the Freenet installed on this computer. If nothing happens, your Freenet may
be older than this feature: try the options above instead.
</p>
</div>
<div class="open-link-option">
Expand Down Expand Up @@ -147,6 +155,18 @@ <h2>Open this link</h2>
return map;
})();
var CONTRACT_KEY_BYTES = 32;

// Apps that hold keys a visitor should keep on their own peer. For these
// the "Use in your browser" button is replaced by the reason:
// try.freenet.org is for trying Freenet out, and anything stored there
// lives on a peer we host. Keyed by the app's web container id, so a
// re-key of the app must update its entry here too.
var LOCAL_ONLY = Object.create(null);
// Ghost Key vault (freenet/ghostkeys, published-contract/contract-id.txt).
LOCAL_ONLY["DLog47hEsrtuGT4N5XCeMBG45m4n1aWM89tBZXue2E1N"] =
"This is the Ghost Key vault, so open it on your own peer. " +
"try.freenet.org is for trying Freenet out, and a Ghost Key kept there lives on a " +
"peer we host instead of your computer.";
// A genuine 32-byte id encodes to at most 44 base58 characters; 64 is a
// generous ceiling above that. Rejecting anything longer before decoding
// keeps a pasted wall of text from making this page do O(n^2) bignum
Expand Down Expand Up @@ -344,6 +364,14 @@ <h2>Open this link</h2>
document.getElementById("open-link-try").href =
"https://try.freenet.org/v1/contract/web/" + contractId + parts.rest;

// Set both ways every time: hashchange can move between a local-only
// app and any other.
var localOnly = LOCAL_ONLY[contractId];
document.getElementById("open-link-try-option").style.display = localOnly ? "none" : "";
var localOnlyNote = document.getElementById("open-link-local-only");
localOnlyNote.textContent = localOnly || "";
localOnlyNote.style.display = localOnly ? "" : "none";

show("open-link-valid");
}

Expand Down
16 changes: 16 additions & 0 deletions scripts/check-links.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
# element. https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment
ALWAYS_VALID_FRAGMENTS = {"top"}

# Pages whose fragment is data their own JavaScript reads, not an anchor:
# /open's is a share link (/open#<contract-id>/<path>...). The page itself must
# still exist.
FRAGMENT_IS_DATA_PAGES = {"/open/index.html"}

MAX_REDIRECTS = 5

# <meta http-equiv=refresh content="0; url=..."> — only meaningful in <head>,
Expand Down Expand Up @@ -254,6 +259,8 @@ def check(root):

if not fragment or fragment.lower() in ALWAYS_VALID_FRAGMENTS:
continue
if target_key in FRAGMENT_IS_DATA_PAGES:
continue
# Check the page's own ids before following any redirect: a real
# page can carry a meta refresh and still be the fragment's home.
if fragment in pages[target_key].ids:
Expand All @@ -262,6 +269,8 @@ def check(root):
if resolved is None:
if reason is not None:
broken.append((source_key, reference, reason))
elif resolved in FRAGMENT_IS_DATA_PAGES:
continue # an alias of /open still hands the fragment to its JS
elif fragment not in pages[resolved].ids:
broken.append((source_key, reference, "no #%s on the target page" % fragment))
return len(pages), broken
Expand Down Expand Up @@ -343,6 +352,10 @@ def write(path, body):
<a href="/caf%C3%A9/#accented">good: fragment through a percent-encoded path</a>
<a href="/caf%C3%A9/#r%C3%A9sum%C3%A9">good: percent-encoded fragment too</a>
<a href="/encoded-alias/#accented">good: alias whose refresh URL is encoded</a>
<a href="/open/#6FzSeAUKcqJrveKyU8RJgGKc5jRB1Z2juvxXtwTA4Em9/">good: /open reads its fragment as data</a>
<a href="/open#6FzSeAUKcqJrveKyU8RJgGKc5jRB1Z2juvxXtwTA4Em9/">good: same, without the slash</a>
<a href="/old-open/#6FzSeAUKcqJrveKyU8RJgGKc5jRB1Z2juvxXtwTA4Em9/">good: through an alias of /open</a>
<a href="/about/#6FzSeAUKcqJrveKyU8RJgGKc5jRB1Z2juvxXtwTA4Em9/">BAD: only /open is exempt</a>
<a href="/%2e%2e/%2e%2e/etc/hostname">BAD: must not escape the output tree</a>
<img srcset="data:image/png;base64,iVBORw0KGgo= 1x">
<a href="https://sitemap-host.example/nope/">BAD: host taken from sitemap.xml</a>
Expand All @@ -355,6 +368,8 @@ def write(path, body):
"</url></urlset>",
)
write("about/index.html", "<p>hi</p>")
write("open/index.html", "<p>reads location.hash</p>")
write("old-open/index.html", '<head><meta http-equiv="refresh" content="0; url=/open/"></head>')
write("old-faq/index.html", '<head><meta http-equiv="refresh" content="0; url=/faq/"></head>')
write("loop-a/index.html", '<head><meta http-equiv="refresh" content="0; url=/loop-b/"></head>')
write("loop-b/index.html", '<head><meta http-equiv="refresh" content="0; url=/loop-a/"></head>')
Expand Down Expand Up @@ -422,6 +437,7 @@ def write(path, body):
"/dangling-alias/#anything",
"/body-refresh/#what-is-freenet",
"/dup-attr/",
"/about/#6FzSeAUKcqJrveKyU8RJgGKc5jRB1Z2juvxXtwTA4Em9/",
"HTTPS://FREENET.ORG/nope/",
"https://freenet.org:443/nope/",
"/%2e%2e/%2e%2e/etc/hostname",
Expand Down
Loading