Skip to content

Commit 6e840f2

Browse files
committed
x25519: move X25519KeyAgreement into a separate file [BREAKING]
1 parent 4fce9d9 commit 6e840f2

4 files changed

Lines changed: 101 additions & 94 deletions

File tree

packages/x25519/keyagreement.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (C) 2020 Dmitry Chestnykh
2+
// MIT License. See LICENSE file for details.
3+
4+
import { KeyAgreement } from "@stablelib/keyagreement";
5+
import { randomBytes, RandomSource } from "@stablelib/random";
6+
import { wipe } from "@stablelib/wipe";
7+
import { PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SHARED_KEY_LENGTH, generateKeyPairFromSeed, sharedKey } from "./x25519";
8+
9+
/** Constants for key agreement */
10+
export const OFFER_MESSAGE_LENGTH = PUBLIC_KEY_LENGTH;
11+
export const ACCEPT_MESSAGE_LENGTH = PUBLIC_KEY_LENGTH;
12+
export const SAVED_STATE_LENGTH = SECRET_KEY_LENGTH;
13+
export const SECRET_SEED_LENGTH = SECRET_KEY_LENGTH;
14+
15+
/**
16+
* X25519 key agreement using ephemeral key pairs.
17+
*
18+
* Note that unless this key agreement is combined with an authentication
19+
* method, such as public key signatures, it's vulnerable to man-in-the-middle
20+
* attacks.
21+
*/
22+
export class X25519KeyAgreement implements KeyAgreement {
23+
readonly offerMessageLength = OFFER_MESSAGE_LENGTH;
24+
readonly acceptMessageLength = ACCEPT_MESSAGE_LENGTH;
25+
readonly sharedKeyLength = SHARED_KEY_LENGTH;
26+
readonly savedStateLength = SAVED_STATE_LENGTH;
27+
28+
private _secretKey: Uint8Array;
29+
private _sharedKey: Uint8Array | undefined;
30+
private _offered = false;
31+
32+
constructor(secretSeed?: Uint8Array, prng?: RandomSource) {
33+
this._secretKey = secretSeed || randomBytes(SECRET_KEY_LENGTH, prng);
34+
}
35+
36+
saveState(): Uint8Array {
37+
return new Uint8Array(this._secretKey);
38+
}
39+
40+
restoreState(savedState: Uint8Array): this {
41+
this._secretKey = new Uint8Array(savedState);
42+
return this;
43+
}
44+
45+
clean(): void {
46+
if (this._secretKey) {
47+
wipe(this._secretKey);
48+
}
49+
if (this._sharedKey) {
50+
wipe(this._sharedKey);
51+
}
52+
}
53+
54+
offer(): Uint8Array {
55+
this._offered = true;
56+
const keyPair = generateKeyPairFromSeed(this._secretKey);
57+
return keyPair.publicKey;
58+
}
59+
60+
accept(offerMsg: Uint8Array): Uint8Array {
61+
if (this._offered) {
62+
throw new Error("X25519KeyAgreement: accept shouldn't be called by offering party");
63+
}
64+
if (offerMsg.length !== this.offerMessageLength) {
65+
throw new Error("X25519KeyAgreement: incorrect offer message length");
66+
}
67+
const keyPair = generateKeyPairFromSeed(this._secretKey);
68+
this._sharedKey = sharedKey(keyPair.secretKey, offerMsg);
69+
wipe(keyPair.secretKey);
70+
return keyPair.publicKey;
71+
}
72+
73+
finish(acceptMsg: Uint8Array): this {
74+
if (acceptMsg.length !== this.acceptMessageLength) {
75+
throw new Error("X25519KeyAgreement: incorrect accept message length");
76+
}
77+
if (!this._secretKey) {
78+
throw new Error("X25519KeyAgreement: no offer state");
79+
}
80+
if (this._sharedKey) {
81+
throw new Error("X25519KeyAgreement: finish was already called");
82+
}
83+
this._sharedKey = sharedKey(this._secretKey, acceptMsg);
84+
return this;
85+
}
86+
87+
getSharedKey(): Uint8Array {
88+
if (!this._sharedKey) {
89+
throw new Error("X25519KeyAgreement: no shared key established");
90+
}
91+
return new Uint8Array(this._sharedKey);
92+
}
93+
}

packages/x25519/x25519.bench.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright (C) 2016 Dmitry Chestnykh
22
// MIT License. See LICENSE file for details.
33

4-
import { scalarMultBase, X25519KeyAgreement } from "./x25519";
4+
import { scalarMultBase } from "./x25519";
5+
import { X25519KeyAgreement } from "./keyagreement";
56
import { benchmark, report, byteSeq } from "@stablelib/benchmark";
67

78
const r = new Uint8Array(32); r[0] = 1;

packages/x25519/x25519.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import { RandomSource } from "@stablelib/random";
55
import { encode, decode } from "@stablelib/hex";
66
import {
7-
scalarMultBase, sharedKey, generateKeyPair, X25519KeyAgreement
7+
scalarMultBase, sharedKey, generateKeyPair
88
} from "./x25519";
9+
import { X25519KeyAgreement } from './keyagreement';
910

1011
describe("x25519.scalarMultBase", () => {
1112
it("should return correct result", () => {

packages/x25519/x25519.ts

Lines changed: 4 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77

88
import { randomBytes, RandomSource } from "@stablelib/random";
99
import { wipe } from "@stablelib/wipe";
10-
import { KeyAgreement } from "@stablelib/keyagreement";
1110

1211
export const PUBLIC_KEY_LENGTH = 32;
1312
export const SECRET_KEY_LENGTH = 32;
1413
export const SHARED_KEY_LENGTH = 32;
1514

16-
// TODO(dchest): some functions ara copies of ../sign/ed25519.
15+
// TODO(dchest): some functions are copies of ../sign/ed25519.
1716
// Find a way to combine them without opening up to public.
1817

1918
// Ported from TweetNaCl.js, which is ported from TweetNaCl
@@ -602,16 +601,16 @@ export function generateKeyPair(prng?: RandomSource): KeyPair {
602601
* From RFC 7748:
603602
*
604603
* > Protocol designers using Diffie-Hellman over the curves defined in
605-
* > this document must not assume "contributory behaviour". Specially,
606-
* > contributory behaviour means that both parties' private keys
604+
* > this document must not assume "contributory behavior". Specially,
605+
* > contributory behavior means that both parties' private keys
607606
* > contribute to the resulting shared key. Since curve25519 and
608607
* > curve448 have cofactors of 8 and 4 (respectively), an input point of
609608
* > small order will eliminate any contribution from the other party's
610609
* > private key. This situation can be detected by checking for the all-
611610
* > zero output, which implementations MAY do, as specified in Section 6.
612611
* > However, a large number of existing implementations do not do this.
613612
*
614-
* Important: the returned key is a raw result of scalar multiplication.
613+
* IMPORTANT: the returned key is a raw result of scalar multiplication.
615614
* To use it as a key material, hash it with a cryptographic hash function.
616615
*/
617616
export function sharedKey(mySecretKey: Uint8Array, theirPublicKey: Uint8Array, rejectZero = false): Uint8Array {
@@ -636,90 +635,3 @@ export function sharedKey(mySecretKey: Uint8Array, theirPublicKey: Uint8Array, r
636635

637636
return result;
638637
}
639-
640-
641-
/** Constants for key agreement */
642-
export const OFFER_MESSAGE_LENGTH = PUBLIC_KEY_LENGTH;
643-
export const ACCEPT_MESSAGE_LENGTH = PUBLIC_KEY_LENGTH;
644-
export const SAVED_STATE_LENGTH = SECRET_KEY_LENGTH;
645-
export const SECRET_SEED_LENGTH = SECRET_KEY_LENGTH;
646-
647-
/**
648-
* X25519 key agreement using ephemeral key pairs.
649-
*
650-
* Note that unless this key agreement is combined withan authentication
651-
* method, such as public key signatures, it's vulnerable to man-in-the-middle
652-
* attacks.
653-
*/
654-
export class X25519KeyAgreement implements KeyAgreement {
655-
readonly offerMessageLength = OFFER_MESSAGE_LENGTH;
656-
readonly acceptMessageLength = ACCEPT_MESSAGE_LENGTH;
657-
readonly sharedKeyLength = SHARED_KEY_LENGTH;
658-
readonly savedStateLength = SAVED_STATE_LENGTH;
659-
660-
private _secretKey: Uint8Array;
661-
private _sharedKey: Uint8Array | undefined;
662-
private _offered = false;
663-
664-
constructor(secretSeed?: Uint8Array, prng?: RandomSource) {
665-
this._secretKey = secretSeed || randomBytes(SECRET_KEY_LENGTH, prng);
666-
}
667-
668-
saveState(): Uint8Array {
669-
return new Uint8Array(this._secretKey);
670-
}
671-
672-
restoreState(savedState: Uint8Array): this {
673-
this._secretKey = new Uint8Array(savedState);
674-
return this;
675-
}
676-
677-
clean(): void {
678-
if (this._secretKey) {
679-
wipe(this._secretKey);
680-
}
681-
if (this._sharedKey) {
682-
wipe(this._sharedKey);
683-
}
684-
}
685-
686-
offer(): Uint8Array {
687-
this._offered = true;
688-
const keyPair = generateKeyPairFromSeed(this._secretKey);
689-
return keyPair.publicKey;
690-
}
691-
692-
accept(offerMsg: Uint8Array): Uint8Array {
693-
if (this._offered) {
694-
throw new Error("X25519KeyAgreement: accept shouldn't be called by offering party");
695-
}
696-
if (offerMsg.length !== this.offerMessageLength) {
697-
throw new Error("X25519KeyAgreement: incorrect offer message length");
698-
}
699-
const keyPair = generateKeyPairFromSeed(this._secretKey);
700-
this._sharedKey = sharedKey(keyPair.secretKey, offerMsg);
701-
wipe(keyPair.secretKey);
702-
return keyPair.publicKey;
703-
}
704-
705-
finish(acceptMsg: Uint8Array): this {
706-
if (acceptMsg.length !== this.acceptMessageLength) {
707-
throw new Error("X25519KeyAgreement: incorrect accept message length");
708-
}
709-
if (!this._secretKey) {
710-
throw new Error("X25519KeyAgreement: no offer state");
711-
}
712-
if (this._sharedKey) {
713-
throw new Error("X25519KeyAgreement: finish was already called");
714-
}
715-
this._sharedKey = sharedKey(this._secretKey, acceptMsg);
716-
return this;
717-
}
718-
719-
getSharedKey(): Uint8Array {
720-
if (!this._sharedKey) {
721-
throw new Error("X25519KeyAgreement: no shared key established");
722-
}
723-
return new Uint8Array(this._sharedKey);
724-
}
725-
}

0 commit comments

Comments
 (0)