diff --git a/web-crypto/sign-verify/ed25519.js b/web-crypto/sign-verify/ed25519.js new file mode 100644 index 00000000..d1c13474 --- /dev/null +++ b/web-crypto/sign-verify/ed25519.js @@ -0,0 +1,74 @@ +(() => { + /* + Store the calculated signature here, so we can verify it later. + */ + let signature; + + /* + Fetch the contents of the "message" textbox, and encode it + in a form we can use for sign operation. + */ + function getMessageEncoding() { + const messageBox = document.querySelector("#ed25519-message"); + let message = messageBox.value; + let enc = new TextEncoder(); + return enc.encode(message); + } + + /* + Get the encoded message-to-sign, sign it and display a representation + of the first part of it in the "signature" element. + */ + async function signMessage(privateKey) { + const signatureValue = document.querySelector(".ed25519 .signature-value"); + signatureValue.classList.remove("valid", "invalid"); + + let encoded = getMessageEncoding(); + signature = await window.crypto.subtle.sign("Ed25519", privateKey, encoded); + + signatureValue.classList.add("fade-in"); + signatureValue.addEventListener("animationend", () => { + signatureValue.classList.remove("fade-in"); + }); + let buffer = new Uint8Array(signature, 0, 5); + signatureValue.textContent = `${buffer}...[${signature.byteLength} bytes total]`; + } + + /* + Fetch the encoded message-to-sign and verify it against the stored signature. + * If it checks out, set the "valid" class on the signature. + * Otherwise set the "invalid" class. + */ + async function verifyMessage(publicKey) { + const signatureValue = document.querySelector(".ed25519 .signature-value"); + signatureValue.classList.remove("valid", "invalid"); + + let encoded = getMessageEncoding(); + let result = await window.crypto.subtle.verify( + "Ed25519", + publicKey, + signature, + encoded + ); + + signatureValue.classList.add(result ? "valid" : "invalid"); + } + + /* + Generate a sign/verify key, then set up event listeners + on the "Sign" and "Verify" buttons. + */ + window.crypto.subtle + .generateKey("Ed25519", true, ["sign", "verify"]) + .then((keyPair) => { + const signButton = document.querySelector(".ed25519 .sign-button"); + signButton.addEventListener("click", () => { + signMessage(keyPair.privateKey); + }); + + const verifyButton = document.querySelector(".ed25519 .verify-button"); + verifyButton.addEventListener("click", () => { + verifyMessage(keyPair.publicKey); + }); + }); +})(); diff --git a/web-crypto/sign-verify/index.html b/web-crypto/sign-verify/index.html index de592ff5..a184a484 100644 --- a/web-crypto/sign-verify/index.html +++ b/web-crypto/sign-verify/index.html @@ -1,9 +1,9 @@ - + Web Crypto API example - + @@ -11,20 +11,62 @@

Web Crypto: sign/verify

-

This page shows the use of the sign() and verify() functions of the Web Crypto API. It contains four separate examples, one for each signing algorithm supported:

- -
+
  • + "Ed25519" + +
  • + +

    Each example has four components:

    Try it:

    @@ -55,12 +104,19 @@

    RSA-PSS

    - + +
    +
    + Signature:
    -
    Signature:
    - - + +
    @@ -69,12 +125,19 @@

    ECDSA

    - +
    -
    Signature:
    - - +
    + Signature: +
    + +
    @@ -83,20 +146,48 @@

    HMAC

    - + +
    +
    + Signature: +
    + + +
    + + +
    +

    Ed25519

    +
    +
    + + +
    +
    + Signature:
    -
    Signature:
    - - + +
    - + diff --git a/web-crypto/sign-verify/style.css b/web-crypto/sign-verify/style.css index 16c9160d..d502f4f6 100644 --- a/web-crypto/sign-verify/style.css +++ b/web-crypto/sign-verify/style.css @@ -1,124 +1,137 @@ /* General setup */ * { - box-sizing: border-box; + box-sizing: border-box; } -html,body { - font-family: sans-serif; - line-height: 1.2rem; +html, +body { + font-family: sans-serif; + line-height: 1.2rem; } /* Layout and styles */ h1 { - color: green; - margin-left: .5rem; + color: green; + margin-left: 0.5rem; } -.description, .sign-verify { - margin: 0 .5rem; +.description, +.sign-verify { + margin: 0 0.5rem; } .description > p { - margin-top: 0; + margin-top: 0; } .sign-verify { - box-shadow: -1px 2px 5px gray; - padding: .2rem .5rem; - margin-bottom: 2rem; + box-shadow: -1px 2px 5px gray; + padding: 0.2rem 0.5rem; + margin-bottom: 2rem; } .sign-verify-controls > * { - margin: .5rem 0; + margin: 0.5rem 0; } input[type="button"] { - width: 5rem; + width: 5rem; } .signature-value { - padding-left: .5rem; - font-family: monospace; + padding-left: 0.5rem; + font-family: monospace; } /* Validity CSS */ .valid { - color: green; + color: green; } .invalid { - color: red; + color: red; } .invalid::after { - content: ' ✖'; + content: " ✖"; } .valid::after { - content: ' ✓'; + content: " ✓"; } /* Whole page grid */ main { - display: grid; - grid-template-columns: 32rem 1fr; - grid-template-rows: 4rem 1fr; + display: grid; + grid-template-columns: 32rem 1fr; + grid-template-rows: 4rem 1fr; } h1 { - grid-column: 1/2; - grid-row: 1; + grid-column: 1/2; + grid-row: 1; } .examples { - grid-column: 1; - grid-row: 2; + grid-column: 1; + grid-row: 2; } .description { - grid-column: 2; - grid-row: 2; + grid-column: 2; + grid-row: 2; } /* sign-verify controls grid */ .sign-verify-controls { - display: grid; - grid-template-columns: 1fr 5rem; - grid-template-rows: 1fr 1fr; + display: grid; + grid-template-columns: 1fr 5rem; + grid-template-rows: 1fr 1fr; } .message-control { - grid-column-start: 1; - grid-row-start: 1; + grid-column-start: 1; + grid-row-start: 1; } .signature { - grid-column-start: 1; - grid-row-start: 2; + grid-column-start: 1; + grid-row-start: 2; } .sign-button { - grid-column-start: 2; - grid-row-start: 1; + grid-column-start: 2; + grid-row-start: 1; } .verify-button { - grid-column-start: 2; - grid-row-start: 2; + grid-column-start: 2; + grid-row-start: 2; } /* Animate output display */ .fade-in { - animation: fadein .5s; + animation: fadein 0.5s; +} + +.caution { + font-weight: bold; +} + +.caution-list-item { + padding: 1em; + background-color: rgba(255, 42, 81, 0.1); + list-style-type: none; + border: 1px solid grey; } @keyframes fadein { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { + opacity: 0; + } + to { + opacity: 1; + } }