Added Wireguard VPN (#23347)

This commit is contained in:
s-hadinger 2025-04-27 20:54:01 +02:00 committed by GitHub
parent 723684bb06
commit 94652ad6ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 6500 additions and 3 deletions

View File

@ -25,9 +25,6 @@
#include <stdlib.h>
#include <Arduino.h>
// #define strcmp_P(x, y) strcmp(x,y)
// #define strcasecmp_P(x,y) strcasecmp(x,y)
// #define pgm_read_byte(x) (*(uint8_t*)(x))
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif

View File

@ -0,0 +1,504 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
(This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.)
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random
Hacker.
{signature of Ty Coon}, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@ -0,0 +1,101 @@
# IniFile
IniFile is an Arduino library for reading ini files. The format is
similar to that seen in Microsoft `.ini` files but the implementation
is completely independent. IniFile is designed to use minimal memory
requirements, and the only buffer used is one supplied by the user,
thus the user remains in charge of memory usage.
The ini file is separated into sections, where the section names are
written inside square brackets. If you don't wish to use sections then
pass a `NULL` pointer for the section name. Under each section are a
set of key-value pairs, separated by an equals sign (`=`). Spaces
around keys and values are ignored, but extra spaces inside the key
are significant. Whitespace inside the value string is preserved; if
leading or trailing whitespace is important you must quote the value
string inside the ini file, and you must strip out the quotes
yourself. If multiple entries for the same key exist inside the
selected section (if using) then only the first value is returned. If
a section is defined more than once only the first is used. The .ini
file can contain comments, which begin with a semicolon (`;`) or hash
(`#`). The user-supplied buffer must be large enough to accomodate the
longest line in the file.
## Example file format
; Semi-colon comment
[network]
mac = 01:23:45:67:89:AB
# hash comment, leading spaces below
gateway = 192.168.1.1
# extraneous spaces before and after key and value
ip = 192.168.1.2
hosts allow = example.com
# A similarly-named section
[network2]
mac = ee:ee:ee:ee:ee:ee
subnet mask=255.255.255.0
; Extra whitespace around the key and value is permitted
; (and ignored)
hosts allow = sloppy.example.com
[misc]
string = 123456789012345678901234567890123456789001234567890
string2 = a string with spaces in it
; This section is a repeat of en existing section and will be ignored.
[network]
mac = 01:23:45:67:89:ab
ip = 192.168.1.2
gateway = 192.168.1.1
## Write support
Write support is a feature that has been requested on several
occasions but as I am no longer using the IniFile library I will not
add this feature. For anyone planning to add such support the
information below may be useful.
One goal of the `IniFile` implementation was to limit the amount of
memory required. For use in embedded systems `malloc` and `new` are
deliberately not used. Another goal was that tasks which take a longer
duration were broken down into smaller chunks of work, eg
`IniFile::getValue(const char* section, const char* key, char* buffer,
int len, IniFileState &state)`. This was because I wanted my `WwwServer`
library, which uses `IniFile`, to avoid interfering with time-critical
code.
I don't think that write support can meet the time-critical goal but
that doesn't prevent its inclusion. I think the way I would choose to
implement write support is to use `IniFile::findKey()` to find where the
desired key is located in the file. I'd then copy everything up to
that point to a temporary file, insert a line for the value and new
key, skip the current line in the existing file (using
`IniFile::readline()`) and then write out the reminder of the existing
file into the temporary file. I'd like to move or rename the temporary
file over the existing file but the Arduino SD library doesn't provide
this functionality; I'd probably just copy the temporary file over the
old one and then delete the temporary one.
The code has been written under a standard Linux environment, using a
compatibility header file to mimic the SD library. This was far more
convenient than doing everything on the Arduino and enabled me to use
`gdb` to debug the code and core dumps. You'll find `arduino_compat.h`
inside the test directory, along with a `Makefile` which can be used for
regression testing. Any proposed changes must pass the regression
tests.
## Contributors
* [Steve Marple](https://github.com/stevemarple)
* [per1234](https://github.com/per1234)
* [OscarVanL](https://github.com/OscarVanL)
* [MikuX2](https://github.com/toybox01)
* [kaixxx](https://github.com/kaixxx)

View File

@ -0,0 +1,135 @@
#include <SD.h>
#include <SPI.h>
#include <IPAddress.h>
#include <IniFile.h>
// The select pin used for the SD card
#define SD_SELECT 10
//#define ETHERNET_SELECT 10
void printErrorMessage(uint8_t e, bool eol = true)
{
switch (e) {
case IniFile::errorNoError:
Serial.print("no error");
break;
case IniFile::errorFileNotFound:
Serial.print("file not found");
break;
case IniFile::errorFileNotOpen:
Serial.print("file not open");
break;
case IniFile::errorBufferTooSmall:
Serial.print("buffer too small");
break;
case IniFile::errorSeekError:
Serial.print("seek error");
break;
case IniFile::errorSectionNotFound:
Serial.print("section not found");
break;
case IniFile::errorKeyNotFound:
Serial.print("key not found");
break;
case IniFile::errorEndOfFile:
Serial.print("end of file");
break;
case IniFile::errorUnknownError:
Serial.print("unknown error");
break;
default:
Serial.print("unknown error value");
break;
}
if (eol)
Serial.println();
}
void setup()
{
// Configure all of the SPI select pins as outputs and make SPI
// devices inactive, otherwise the earlier init routines may fail
// for devices which have not yet been configured.
pinMode(SD_SELECT, OUTPUT);
digitalWrite(SD_SELECT, HIGH); // disable SD card
// pinMode(ETHERNET_SELECT, OUTPUT);
// digitalWrite(ETHERNET_SELECT, HIGH); // disable Ethernet
const size_t bufferLen = 80;
char buffer[bufferLen];
const char *filename = "/lunch.ini";
Serial.begin(9600);
SPI.begin();
if (!SD.begin(SD_SELECT))
while (1)
Serial.println("SD.begin() failed");
IniFile ini(filename);
if (!ini.open()) {
Serial.print("Ini file ");
Serial.print(filename);
Serial.println(" does not exist");
// Cannot do anything else
while (1)
;
}
Serial.println("Ini file exists");
// Check the file is valid. This can be used to warn if any lines
// are longer than the buffer.
if (!ini.validate(buffer, bufferLen)) {
Serial.print("ini file ");
Serial.print(ini.getFilename());
Serial.print(" not valid: ");
printErrorMessage(ini.getError());
// Cannot do anything else
while (1)
;
}
// Browse through all sections and print contents:
IniFileState state;
char sectName[bufferLen];
Serial.println();
while (ini.browseSections(sectName, bufferLen, state)) {
Serial.print("> ");
Serial.print(sectName);
if (ini.getValue(sectName, "meal", buffer, bufferLen)) {
Serial.print(" eats ");
Serial.print(buffer);
} else
Serial.print(" eats nothing");
if (ini.getValue(sectName, "drinks", buffer, bufferLen)) {
Serial.print(", drinks ");
Serial.print(buffer);
} else
Serial.print(", drinks nothing");
if (ini.getValue(sectName, "dessert", buffer, bufferLen)) {
Serial.print(" and has ");
Serial.print(buffer);
Serial.println(" for dessert.");
} else
Serial.println(" and has no dessert.");
}
// finished!
Serial.println();
printErrorMessage(ini.getError());
// Cannot do anything else
while (1)
;
}
void loop()
{
}

View File

@ -0,0 +1,35 @@
# IniFile browsing example
Shows how to browse through an ini file with an unknown number of sections
which all contain the same elements. This can be useful to store different
profiles which are selectable on your device.
## Instructions for use
* Copy the `lunch.ini` file to the root directory of your (micro)SD card.
* Modify the `IniBrowseExample.ino` file so that `SD_SELECT` defines
the correct pin number.
* Compile and upload the sketch.
## Expected output
It may take a few seconds from the sketch starting before anything is
printed to the serial port, be patient. If the sketch runs correctly
the output should appear as below:
Ini file exists
> Karen eats burger, drinks beer and has chocolate for dessert.
> Peter eats falafel, drinks tea without milk and has vegan icecream for dessert.
> Noel eats sushi, drinks water and has no dessert.
> Jessica eats sandwich, drinks nothing and has muffin for dessert.
end of file
If the SD card is missing or cannot be read the sketch will print:
SD.begin() failed

View File

@ -0,0 +1,22 @@
; A list of settings/profiles stored as sections.
; Each profile has the same elements.
[Karen]
meal = burger
drinks = beer
dessert = chocolate
[Peter]
meal = falafel
drinks = tea without milk
dessert = vegan icecream
[Noel]
meal = sushi
drinks = water
; note: dessert is missing here
[Jessica]
meal = sandwich
; no drinks
dessert = muffin

View File

@ -0,0 +1,143 @@
#include <SD.h>
#include <SPI.h>
#include <IPAddress.h>
#include <IniFile.h>
// The select pin used for the SD card
//#define SD_SELECT 4
#define SD_SELECT 22
#define ETHERNET_SELECT 10
void printErrorMessage(uint8_t e, bool eol = true)
{
switch (e) {
case IniFile::errorNoError:
Serial.print("no error");
break;
case IniFile::errorFileNotFound:
Serial.print("file not found");
break;
case IniFile::errorFileNotOpen:
Serial.print("file not open");
break;
case IniFile::errorBufferTooSmall:
Serial.print("buffer too small");
break;
case IniFile::errorSeekError:
Serial.print("seek error");
break;
case IniFile::errorSectionNotFound:
Serial.print("section not found");
break;
case IniFile::errorKeyNotFound:
Serial.print("key not found");
break;
case IniFile::errorEndOfFile:
Serial.print("end of file");
break;
case IniFile::errorUnknownError:
Serial.print("unknown error");
break;
default:
Serial.print("unknown error value");
break;
}
if (eol)
Serial.println();
}
void setup()
{
// Configure all of the SPI select pins as outputs and make SPI
// devices inactive, otherwise the earlier init routines may fail
// for devices which have not yet been configured.
pinMode(SD_SELECT, OUTPUT);
digitalWrite(SD_SELECT, HIGH); // disable SD card
pinMode(ETHERNET_SELECT, OUTPUT);
digitalWrite(ETHERNET_SELECT, HIGH); // disable Ethernet
const size_t bufferLen = 80;
char buffer[bufferLen];
const char *filename = "/net.ini";
Serial.begin(9600);
SPI.begin();
if (!SD.begin(SD_SELECT))
while (1)
Serial.println("SD.begin() failed");
IniFile ini(filename);
if (!ini.open()) {
Serial.print("Ini file ");
Serial.print(filename);
Serial.println(" does not exist");
// Cannot do anything else
while (1)
;
}
Serial.println("Ini file exists");
// Check the file is valid. This can be used to warn if any lines
// are longer than the buffer.
if (!ini.validate(buffer, bufferLen)) {
Serial.print("ini file ");
Serial.print(ini.getFilename());
Serial.print(" not valid: ");
printErrorMessage(ini.getError());
// Cannot do anything else
while (1)
;
}
// Fetch a value from a key which is present
if (ini.getValue("network", "mac", buffer, bufferLen)) {
Serial.print("section 'network' has an entry 'mac' with value ");
Serial.println(buffer);
}
else {
Serial.print("Could not read 'mac' from section 'network', error was ");
printErrorMessage(ini.getError());
}
// Try fetching a value from a missing key (but section is present)
if (ini.getValue("network", "nosuchkey", buffer, bufferLen)) {
Serial.print("section 'network' has an entry 'nosuchkey' with value ");
Serial.println(buffer);
}
else {
Serial.print("Could not read 'nosuchkey' from section 'network', error was ");
printErrorMessage(ini.getError());
}
// Try fetching a key from a section which is not present
if (ini.getValue("nosuchsection", "nosuchkey", buffer, bufferLen)) {
Serial.print("section 'nosuchsection' has an entry 'nosuchkey' with value ");
Serial.println(buffer);
}
else {
Serial.print("Could not read 'nosuchkey' from section 'nosuchsection', error was ");
printErrorMessage(ini.getError());
}
// Fetch a boolean value
bool allowPut; // variable where result will be stored
bool found = ini.getValue("/upload", "allow put", buffer, bufferLen, allowPut);
if (found) {
Serial.print("The value of 'allow put' in section '/upload' is ");
// Print value, converting boolean to a string
Serial.println(allowPut ? "TRUE" : "FALSE");
}
else {
Serial.print("Could not get the value of 'allow put' in section '/upload': ");
printErrorMessage(ini.getError());
}
}
void loop()
{
}

View File

@ -0,0 +1,29 @@
# IniFile example
## Instructions for use
* Copy the `net.ini` file to the root directory of your (micro)SD card.
* Modify the `IniFileExample.ino` file so that `SD_SELECT` defines
the correct pin number.
* Compile and upload the sketch.
## Expected output
It may take a few seconds from the sketch starting before anything is
printed to the serial port, be patient. If the sketch runs correctly
the output should appear as below:
Ini file exists
section 'network' has an entry 'mac' with value 01:23:45:67:89:AB
Could not read 'nosuchkey' from section 'network', error was key not found
Could not read 'nosuchkey' from section 'nosuchsection', error was section not found
The value of 'allow put' in section '/upload' is TRUE
If the SD card is missing or cannot be read the sketch will print:
SD.begin() failed

View File

@ -0,0 +1,68 @@
; Semi-colon comment
[network]
mac = 01:23:45:67:89:AB
# hash comment, leading spaces below
gateway = 192.168.1.1
# extraneous spaces before and after key and value
ip = 192.168.1.2
hosts allow = example.com
# A similarly-named section
[network2]
mac = ee:ee:ee:ee:ee:ee
subnet mask=255.255.255.0
; Test extra whitespace in keys and value
hosts allow = sloppy.example.com
[misc]
string = 123456789012345678901234567890123456789001234567890
string2 = a string with spaces in it
; ini file for WwwServerExample
[mime types]
default = text/plain
htm = text/html
bin = application/octet-stream
pdf = application/pdf
[/]
; no access to root of SD filesystem
handler = default
error document 403 = /errordoc/403.htm
[/www.ini]
handler = default
[/data]
handler = default
[/data/private]
; Block access to this directory
handler = prohibit
error document 403 = /data/private/403.htm
[/data/noaccess.txt]
; Block access to this file
handler = prohibit
[/status]
; built-in status handler
handler = status
[/cgi]
; User-defined handler
handler = cgi
[/src]
; A redirect
handler = temporary redirect
location = http://github.com/stevemarple/WwwServer
[/upload]
allow put = true

View File

@ -0,0 +1,9 @@
name=IniFile
version=modified by Tasmota, based on 1.3.0
author=Steve Marple <stevemarple@googlemail.com>, Stephan Hadiger
maintainer=Steve Marple <stevemarple@googlemail.com>, Stephan Hadiger
sentence=Library to read and parse .ini files, adapted for Tasmota
paragraph=IniFile is a library to read and parse .ini files as used by Microsoft Windows. IniFile is designed to use minimal memory requirements, and the only buffer used is one supplied by the user, thus the user remains in charge of memory usage. GNU LGPL v2.1.
category=Other
url=https://github.com/stevemarple/IniFile
architectures=*

View File

@ -0,0 +1,494 @@
#include "IniFile.h"
#include <Arduino.h>
#include "base64.hpp"
//**************************************************************************************************************
// enable AddLog support within a C++ library
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
//**************************************************************************************************************
extern FS *ffsp;
IniFile::IniFile(File &file)
{
_file = file;
}
IniFile::~IniFile()
{
//if (_file)
// _file.close();
}
IniFile::error_t IniFile::getError(void)
{
return _error;
}
void IniFile::clearError(void)
{
_error = errorNoError;
}
bool IniFile::getValue(const char* section, const char* key, IniFileState &state)
{
char *cp = nullptr;
char *bufptr = buffer;
bool done = false;
if (!_file) {
_error = errorFileNotOpen;
return true;
}
switch (state.getValueState) {
case IniFileState::funcUnset:
state.getValueState = (section == NULL ? IniFileState::funcFindKey
: IniFileState::funcFindSection);
state.readLinePosition = 0;
break;
case IniFileState::funcFindSection:
if (findSection(section, state)) {
if (_error != errorNoError)
return true;
state.getValueState = IniFileState::funcFindKey;
}
break;
case IniFileState::funcFindKey:
if (findKey(section, key, &cp, state)) {
if (_error != errorNoError)
return true;
// Found key line in correct section
cp = skipWhiteSpace(cp);
removeTrailingWhiteSpace(cp);
// Copy from cp to buffer, but the strings overlap so strcpy is out
while (*cp != '\0')
*bufptr++ = *cp++;
*bufptr = '\0';
return true;
}
break;
default:
// How did this happen?
_error = errorUnknownError;
done = true;
break;
}
return done;
}
bool IniFile::getValue(const char* section, const char* key)
{
IniFileState state;
while (!getValue(section, key, state))
;
return _error == errorNoError;
}
bool IniFile::getValueStr(const char* section, const char* key, char *value, size_t vlen)
{
if (!getValue(section, key)) return false; // error
if (strlen(buffer) >= vlen) return false;
strcpy(value, buffer);
return true;
}
bool IniFile::getValueString(const char* section, const char* key, String &value)
{
if (!getValue(section, key)) return false; // error
value = buffer;
return true;
}
// For true accept: true, yes, 1
// For false accept: false, no, 0
bool IniFile::getValueBool(const char* section, const char* key, bool& val)
{
if (!getValue(section, key))
return false; // error
if (strcasecmp_P(buffer, PSTR("true")) == 0 ||
strcasecmp_P(buffer, PSTR("yes")) == 0 ||
strcasecmp_P(buffer, PSTR("1")) == 0) {
val = true;
return true;
}
if (strcasecmp_P(buffer, PSTR("false")) == 0 ||
strcasecmp_P(buffer, PSTR("no")) == 0 ||
strcasecmp_P(buffer, PSTR("0")) == 0) {
val = false;
return true;
}
return false; // does not match any known strings
}
bool IniFile::getValueInt(const char* section, const char* key, int32_t& val)
{
if (!getValue(section, key)) return false; // error
val = atoi(buffer);
return true;
}
bool IniFile::getValueUInt16(const char* section, const char* key, uint16_t& val)
{
int32_t val32;
if (!getValue(section, key)) return false; // error
val32 = atoi(buffer);
if (val32 < 0 || val32 > 65535) return false;
val = (uint16_t) val32;
return true;
}
bool IniFile::getValueFloat(const char* section, const char* key, float & val)
{
if (!getValue(section, key))
return false; // error
char *endptr;
float tmp = strtod(buffer, &endptr);
if (endptr == buffer)
return false; // no conversion
if (*endptr == '\0') {
val = tmp;
return true; // valid conversion
}
// buffer has trailing non-numeric characters, and since the buffer
// already had whitespace removed discard the entire results
return false;
}
bool IniFile::getIPAddress(const char* section, const char* key, ip_addr_t *ip)
{
if (!getValue(section, key)) return false; // error
IPAddress ipaddr;
if (!ipaddr.fromString(buffer)) { return false; }
#ifdef ESP32
ipaddr.to_ip_addr_t(ip);
#else
*ip = ipaddr;
#endif
return true;
}
bool IniFile::getMACAddress(const char* section, const char* key,
uint8_t mac[6])
{
// Need 18 chars: 6 * 2 hex digits, 5 : or - and a null char
if (bufferLen < 18)
return false;
if (!getValue(section, key))
return false; // error
int i = 0;
char* cp = buffer;
memset(mac, 0, 6);
while (*cp != '\0' && i < 6) {
if (*cp == ':' || *cp == '-') {
++i;
++cp;
continue;
}
if (isdigit(*cp)) {
mac[i] *= 16; // working in hex!
mac[i] += (*cp - '0');
}
else {
if (isxdigit(*cp)) {
mac[i] *= 16; // working in hex!
mac[i] += (toupper(*cp) - 55); // convert A to 0xA, F to 0xF
}
else {
memset(mac, 0, 6);
return false;
}
}
++cp;
}
return true;
}
bool IniFile::getValueBase64(const char* section, const char* key, uint8_t *value, size_t vlen)
{
if (!getValue(section, key)) return false; // error
size_t len = decode_base64_length((unsigned char*)buffer);
if (len != vlen) return false;
len = decode_base64((unsigned char*)buffer, value);
return true;
}
bool IniFile::parseCIDR(String& cidr, ip_addr_t *ip, ip_addr_t *mask)
{
int32_t slash = cidr.indexOf('/');
if (slash < 0) { return false; }
IPAddress ipaddr;
if (!ipaddr.fromString(cidr.substring(0, slash))) { return false; }
#ifdef ESP32
ipaddr.to_ip_addr_t(ip);
#else
*ip = ipaddr;
#endif
int32_t prefixLen = cidr.substring(slash + 1).toInt();
if (prefixLen < 0 || prefixLen > 32) { return false; }
IPAddress maskaddr((prefixLen <= 0) ? 0 : (0xFFFFFFFF >> (32 - prefixLen)));
#ifdef ESP32
maskaddr.to_ip_addr_t(mask);
#else
*mask = maskaddr;
#endif
return true;
}
bool IniFile::getCIDR(const char* section, const char* key, ip_addr_t *ip, ip_addr_t *mask)
{
String cidr;
if (!getValueString(section, key, cidr)) return false; // error
return parseCIDR(cidr, ip, mask);
}
bool IniFile::getDomainPort(const char* section, const char* key, String &domain, uint16_t &port)
{
if (!getValueString(section, key, domain)) return false; // error
int32_t colon = domain.indexOf(':');
if (colon < 0) { return false; }
port = domain.substring(colon + 1).toInt();
domain = domain.substring(0, colon);
return true;
}
// From the file location saved in 'state' look for the next section and read its name.
// The name will be in the buffer. Returns false if no section found.
bool IniFile::browseSections(IniFileState &state)
{
error_t err = errorNoError;
char *bufptr = &buffer[0];
do {
err = IniFile::readLine(_file, state.readLinePosition);
if (err != errorNoError) {
// end of file or other error
_error = err;
return false;
} else {
char *cp = skipWhiteSpace(buffer);
if (*cp == '[') {
// Found a section, read the name
++cp;
cp = skipWhiteSpace(cp);
char *ep = strchr(cp, ']');
if (ep != NULL) {
*ep = '\0'; // make ] be end of string
removeTrailingWhiteSpace(cp);
// Copy from cp to buffer, but the strings overlap so strcpy is out
while (*cp != '\0')
*bufptr++ = *cp++;
*bufptr = '\0';
_error = errorNoError;
return true;
}
}
}
// continue searching
} while (err == errorNoError);
// we should never get here...
_error = err;
return false;
}
IniFile::error_t IniFile::readLine(File &file, uint32_t &pos)
{
if (!file)
return errorFileNotOpen;
if (bufferLen < 3)
return errorBufferTooSmall;
if (!file.seek(pos))
return errorSeekError;
size_t bytesRead = file.readBytes(buffer, bufferLen);
if (!bytesRead) {
buffer[0] = '\0';
//return 1; // done
return errorEndOfFile;
}
for (size_t i = 0; i < bytesRead && i < bufferLen-1; ++i) {
// Test for '\n' with optional '\r' too
// if (endOfLineTest(i, '\n', '\r')
if (buffer[i] == '\n' || buffer[i] == '\r') {
char match = buffer[i];
char otherNewline = (match == '\n' ? '\r' : '\n');
// end of line, discard any trailing character of the other sort
// of newline
buffer[i] = '\0';
if (buffer[i+1] == otherNewline)
++i;
pos += (i + 1); // skip past newline(s)
//return (i+1 == bytesRead && !file.available());
return errorNoError;
}
}
if (!file.available()) {
// end of file without a newline
buffer[bytesRead] = '\0';
// return 1; //done
return errorEndOfFile;
}
buffer[bufferLen-1] = '\0'; // terminate the string
return errorBufferTooSmall;
}
bool IniFile::isCommentChar(char c)
{
return (c == ';' || c == '#');
}
char* IniFile::skipWhiteSpace(char* str)
{
char *cp = str;
if (cp)
while (isspace(*cp))
++cp;
return cp;
}
void IniFile::removeTrailingWhiteSpace(char* str)
{
if (str == nullptr)
return;
char *cp = str + strlen(str) - 1;
while (cp >= str && isspace(*cp))
*cp-- = '\0';
}
bool IniFile::findSection(const char* section, IniFileState &state)
{
if (section == NULL) {
_error = errorSectionNotFound;
return true;
}
error_t err = IniFile::readLine(_file, state.readLinePosition);
if (err != errorNoError && err != errorEndOfFile) {
// Signal to caller to stop looking and any error value
_error = err;
return true;
}
char *cp = skipWhiteSpace(buffer);
//if (isCommentChar(*cp))
//return (done ? errorSectionNotFound : 0);
if (isCommentChar(*cp)) {
// return (err == errorEndOfFile ? errorSectionNotFound : errorNoError);
if (err == errorEndOfFile) {
_error = errorSectionNotFound;
return true;
}
else
return false; // Continue searching
}
if (*cp == '[') {
// Start of section
++cp;
cp = skipWhiteSpace(cp);
char *ep = strchr(cp, ']');
if (ep != NULL) {
*ep = '\0'; // make ] be end of string
removeTrailingWhiteSpace(cp);
if (strcmp(cp, section) == 0) {
_error = errorNoError;
return true;
}
}
}
// Not a valid section line
//return (done ? errorSectionNotFound : 0);
if (err == errorEndOfFile) {
_error = errorSectionNotFound;
return true;
}
return false;
}
// From the current file location look for the matching key. If
// section is non-NULL don't look in the next section
bool IniFile::findKey(const char* section, const char* key,
char** keyptr,
IniFileState &state)
{
if (key == NULL || *key == '\0') {
_error = errorKeyNotFound;
return true;
}
error_t err = IniFile::readLine(_file, state.readLinePosition);
if (err != errorNoError && err != errorEndOfFile) {
_error = err;
return true;
}
char *cp = skipWhiteSpace(buffer);
// if (isCommentChar(*cp))
// return (done ? errorKeyNotFound : 0);
if (isCommentChar(*cp)) {
if (err == errorEndOfFile) {
_error = errorKeyNotFound;
return true;
}
else
return false; // Continue searching
}
if (section && *cp == '[') {
// Start of a new section
_error = errorKeyNotFound;
return true;
}
// Find '='
char *ep = strchr(cp, '=');
if (ep != NULL) {
*ep = '\0'; // make = be the end of string
removeTrailingWhiteSpace(cp);
if (strcmp(cp, key) == 0) {
*keyptr = ep + 1;
_error = errorNoError;
return true;
}
}
// Not the valid key line
if (err == errorEndOfFile) {
_error = errorKeyNotFound;
return true;
}
return false;
}
IniFileState::IniFileState()
{
readLinePosition = 0;
getValueState = funcUnset;
}

View File

@ -0,0 +1,117 @@
#ifndef _INIFILE_H
#define _INIFILE_H
#include <stdint.h>
#include <FS.h>
#include "IPAddress.h"
#define INIFILE_VERSION "1.3.0"
class IniFileState;
class IniFile {
public:
enum error_t {
errorNoError = 0,
errorFileNotFound,
errorFileNotOpen,
errorBufferTooSmall,
errorSeekError,
errorSectionNotFound,
errorKeyNotFound,
errorEndOfFile,
errorUnknownError,
};
// Create an IniFile object. It isn't opened until open() is called on it.
IniFile(File &file);
~IniFile();
error_t getError(void);
void clearError(void);
// Get value from the file, but split into many short tasks. Return
// value: false means continue, true means stop. Call getError() to
// find out if any error
bool getValue(const char* section, const char* key, IniFileState &state);
// Get value, as one big task. Return = true means value is present
// in buffer
bool getValue(const char* section, const char* key);
// Get the value as a string, storing the result in a new buffer
// (not the working buffer)
bool getValueStr(const char* section, const char* key, char *value, size_t vlen);
bool getValueString(const char* section, const char* key, String &value);
// Get a boolean value
bool getValueBool(const char* section, const char* key, bool& b);
// Get an integer value
bool getValueInt(const char* section, const char* key, int32_t& val);
// Get an uint16_t value
bool getValueUInt16(const char* section, const char* key, uint16_t& val);
// Get a float value
bool getValueFloat(const char* section, const char* key, float& val);
bool getIPAddress(const char* section, const char* key, ip_addr_t *ip);
bool getMACAddress(const char* section, const char* key,uint8_t mac[6]);
bool getValueBase64(const char* section, const char* key, uint8_t *value, size_t vlen);
static bool parseCIDR(String& str, ip_addr_t *ip, ip_addr_t *mask);
bool getCIDR(const char* section, const char* key, ip_addr_t *ip, ip_addr_t *mask);
bool getDomainPort(const char* section, const char* key, String &domain, uint16_t &port);
// From the file location saved in 'state' look for the next section and read its name.
// The name will be in the buffer. Returns false if no section found.
bool browseSections(IniFileState &state);
// Utility function to read a line from a file, make available to all
//static int8_t readLine(File &file, char *buffer, size_t len, uint32_t &pos);
error_t readLine(File &file, uint32_t &pos);
static bool isCommentChar(char c);
static char* skipWhiteSpace(char* str);
static void removeTrailingWhiteSpace(char* str);
protected:
// True means stop looking, false means not yet found
bool findSection(const char* section, IniFileState &state);
bool findKey(const char* section, const char* key, char** keyptr, IniFileState &state);
private:
String _filename;
error_t _error;
File _file;
static constexpr size_t bufferLen = 80;
char buffer[bufferLen];
};
class IniFileState {
public:
IniFileState();
private:
enum {funcUnset = 0,
funcFindSection,
funcFindKey,
};
uint32_t readLinePosition;
uint8_t getValueState;
friend class IniFile;
};
#endif

View File

@ -0,0 +1,32 @@
Copyright (c) 2021 Kenta Ida (fuga@fugafuga.org)
Copyright (c) 2022 Tomoyuki Sakurai (y@trombik.org)
Copyright (c) 2023-2024 Simone Rossetto (simros85@gmail.com)
The original license is below:
Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* 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.
* Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
its contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
Author: Daniel Hope <daniel.hope@smartalock.com>

View File

@ -0,0 +1,55 @@
# WireGuard implementation for ESPHome
This is an implementation of [WireGuard&reg;](https://www.wireguard.com/) VPN
for [ESPHome](https://esphome.io/), based on
[Wireguard Implementation for ESP-IDF](https://github.com/trombik/esp_wireguard)
(by [@trombik](https://github.com/trombik)).
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/droscy/library/esp_wireguard.svg)](https://registry.platformio.org/libraries/droscy/esp_wireguard)
## Usage
Please refer to the official documentation of [WireGuard Component](https://esphome.io/components/wireguard)
in ESPHome website.
## Compatibility
This code targets only ESPHome and has been tested on the following platforms:
* ESP32 (with both frameworks)
* ESP8266
* LibreTiny (with `bk72` microcontrollers only)
## References
For additional information see:
* the original feature-request [esphome/feature-requests#1444](https://github.com/esphome/feature-requests/issues/1444)
* the first pull-request [esphome/esphome#4256](https://github.com/esphome/esphome/pull/4256)
* `esp8266` support [esphome/esphome#6365](https://github.com/esphome/esphome/pull/6365)
* LibreTiny support [droscy/esp_wireguard#4](https://github.com/droscy/esp_wireguard/pull/4)
## License
BSD 3-Clause License (SPDX ID: BSD-3-Clause)
This project is licensed under [BSD 3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html)
except where explicitly written in files themselves or when other license files state differently.
"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld.
Please see ["WireGuard" Trademark Usage Policy](https://www.wireguard.com/trademark-policy/)
for additional information.
## Authors
* Simone Rossetto (simros85@gmail.com)
* Tomoyuki Sakurai (y@trombik.org)
* Daniel Hope (daniel.hope@smartalock.com)
* Kenta Ida (fuga@fugafuga.org)
* Matthew Dempsky
* D. J. Bernstein

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
* Copyright (c) 2023-2024 Simone Rossetto <simros85@gmail.com>
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
#if !defined(__ESP_WIREGUARD__H__)
#define __ESP_WIREGUARD__H__
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_wireguard_err.h"
#include <stdint.h>
#include <time.h>
#include <lwip/netif.h>
#define WG_KEY_LEN (32)
#define WG_B64_KEY_LEN (4 * ((WG_KEY_LEN + 2) / 3))
typedef uint8_t wg_key_t[WG_KEY_LEN];
typedef struct {
/* interface config */
wg_key_t private_key2; /**< private key generated by wg genkey. Required. */
uint16_t listen_port; /**< a 16-bit port for listening */
uint32_t fw_mark; /**< a 32-bit fwmark for outgoing packets */
/* peer config */
wg_key_t public_key2; /**< public key calculated by wg pubkey from a private key. Required. */
wg_key_t preshared_key2; /**< preshared key generated by wg genpsk. */
ip_addr_t address2; /**< a local IP address. */
ip_addr_t subnet; /**< a subnet mask of the local IP address. */
ip_addr_t netmask2; /**< the global subnet for the netif. */
const char* endpoint; /**< an endpoint IP address or hostname. */
ip_addr_t endpoint_ip; /**< endpoint IP address (internal use, resolved through dns query) */
uint16_t port; /**< a port number of remote endpoint. Default is 51820. */
uint16_t persistent_keepalive; /**< a seconds interval, between 1 and 65535 inclusive, of how often to send an
authenticated empty packet to the peer for the purpose of keeping a stateful
firewall or NAT mapping valid persistently. Set zero to disable the feature.
Default is zero. */
} wireguard_config_t;
typedef struct {
wireguard_config_t* config; /**< a pointer to wireguard config */
struct netif* netif; /**< a pointer to configured netif */
} wireguard_ctx_t;
/**
* @brief Initialize WireGuard
*
* Call this function to initialize the context of WireGuard.
*
* Do not call this function multiple times.
*
* To connect to other peer, use `esp_wireguard_disconnect()`, and
* `esp_wireguard_init()` with a new configuration. To reconnect to
* the same peer just use `esp_wireguard_disconnect()` and then
* `esp_wireguard_connect()`.
*
* @param config WireGuard configuration.
* @param[out] ctx Context of WireGuard.
*
* @return
* - ESP_OK: Successfully initilized WireGuard interface.
* - ESP_ERR_INVALID_ARG: given argument is invalid.
* - ESP_ERR_INVALID_STATE: hostname dns resolution cannot start
* - ESP_FAIL: Other error.
*/
esp_err_t esp_wireguard_init(wireguard_config_t *config, wireguard_ctx_t *ctx);
/**
* @brief Create a WireGuard interface and start establishing the connection
* to the peer.
*
* Call this function to start establishing the connection. Note that `ESP_OK`
* does not mean the connection is established. To see if the connection is
* established, or the peer is up, use `esp_wireguard_peer_is_up()`.
*
* Do not call this function multiple times.
*
* @param ctx Context of WireGuard.
* @return
* - ESP_OK on success.
* - ESP_ERR_INVALID_ARG if input arguments are invalid
* - ESP_ERR_RETRY dns query still ongoing for endpoint hostname resolution (retry connection)
* - ESP_ERR_INVALID_IP if endpoint IP address is missing or invalid (dns query failed)
* - ESP_FAIL on failure.
*/
esp_err_t esp_wireguard_connect(wireguard_ctx_t *ctx);
/**
* @brief Test if the peer is up.
* @param ctx Context of WireGuard
* @return
* - ESP_OK on peer up.
* - ESP_ERR_INVALID_ARG if ctx is NULL.
* - ESP_FAIL on peer still down.
*/
esp_err_t esp_wireguard_peer_is_up(const wireguard_ctx_t *ctx);
/**
* @brief Get timestamp of the latest handshake (with seconds resolution since unix epoch)
* @param ctx Context of WireGuard
* @param result the output timestamp
* @return
* - ESP_OK on success
* - ESP_FAIL if no handshake already completed
* - ESP_ERR_INVALID_ARG if ctx is NULL
* - ESP_ERR_INVALID_STATE if data inside ctx is not valid
*/
esp_err_t esp_wireguard_latest_handshake(const wireguard_ctx_t *ctx, time_t *result);
/**
* @brief Add new allowed IP/mask to the list of allowed ip/mask
* @param ctx Context of WireGuard
* @param allowed_ip The new IP to be allowed through tunnel
* @param allowed_ip_mask The mask of the new IP
* @return
* - ESP_OK on success
* - ESP_FAIL if the adding failed
* - ESP_ERR_INVALID_ARG if ctx, allowed_ip or allowed_ip_mask are invalid or empty
* - ESP_ERR_INVALID_STATE if data inside ctx is not valid
*/
esp_err_t esp_wireguard_add_allowed_ip(const wireguard_ctx_t *ctx, const ip_addr_t& allowed_ip, const ip_addr_t& allowed_ip_mask);
/**
* @brief Disconnect from the peer
*
* @param ctx Context of WireGuard.
* @return
* - ESP_OK on success.
*/
esp_err_t esp_wireguard_disconnect(wireguard_ctx_t *ctx);
#ifdef __cplusplus
}
#endif
#endif
// vim: expandtab tabstop=4

View File

@ -0,0 +1,55 @@
{
"name": "esp_wireguard_tasmota",
"version": "0.4.2",
"description": "WireGuard implementation for Tasmota, based on the version for ESPHome",
"keywords":[
"tasmota",
"communication",
"network",
"wireguard",
"vpn"
],
"authors":[
{
"name": "Stephan Hadinger",
"email": "stephan.hadinger@gmail.com",
"url": "https://github.com/arendst/Tasmota",
"maintainer": true
},
{
"name": "Simone Rossetto",
"email": "simros85@gmail.com",
"url": "https://github.com/droscy",
"maintainer": true
},
{
"name": "Tomoyuki Sakurai",
"email": "y@trombik.org",
"url": "https://github.com/trombik"
},
{
"name": "Daniel Hope",
"email": "daniel.hope@smartalock.com",
"url": "https://github.com/smartalock"
},
{
"name": "Kenta Ida",
"email": "fuga@fugafuga.org"
},
{
"name": "Matthew Dempsky"
},
{
"name": "D. J. Bernstein"
}
],
"license": "BSD-3-Clause",
"platforms": [
"espressif32",
"espressif8266"
],
"frameworks":[
"espidf",
"arduino"
]
}

View File

@ -0,0 +1,25 @@
#include "crypto.h"
#include <stdlib.h>
#include <stdint.h>
void crypto_zero(void *dest, size_t len) {
volatile uint8_t *p = (uint8_t *)dest;
while (len--) {
*p++ = 0;
}
}
bool crypto_equal(const void *a, const void *b, size_t size) {
uint8_t neq = 0;
while (size > 0) {
neq |= *(uint8_t *)a ^ *(uint8_t *)b;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpointer-arith"
a += 1;
b += 1;
#pragma GCC diagnostic pop
size -= 1;
}
return (neq) ? false : true;
}

View File

@ -0,0 +1,106 @@
#ifndef _CRYPTO_H_
#define _CRYPTO_H_
#ifdef __cplusplus
extern "C" {
#endif
// BLAKE2S IMPLEMENTATION
#include "crypto/refc/blake2s.h"
#define wireguard_blake2s_ctx blake2s_ctx
#define wireguard_blake2s_init(ctx,outlen,key,keylen) blake2s_init(ctx,outlen,key,keylen)
#define wireguard_blake2s_update(ctx,in,inlen) blake2s_update(ctx,in,inlen)
#define wireguard_blake2s_final(ctx,out) blake2s_final(ctx,out)
#define wireguard_blake2s(out,outlen,key,keylen,in,inlen) blake2s(out,outlen,key,keylen,in,inlen)
// X25519 IMPLEMENTATION
int crypto_scalarmult_curve25519(unsigned char *q, const unsigned char *n,const unsigned char *p);
#define wireguard_x25519(a,b,c) crypto_scalarmult_curve25519(a,b,c)
// CHACHA20POLY1305 IMPLEMENTATION
#include "crypto/refc/chacha20poly1305.h"
#define wireguard_aead_encrypt(dst,src,srclen,ad,adlen,nonce,key) chacha20poly1305_encrypt(dst,src,srclen,ad,adlen,nonce,key)
#define wireguard_aead_decrypt(dst,src,srclen,ad,adlen,nonce,key) chacha20poly1305_decrypt(dst,src,srclen,ad,adlen,nonce,key)
#define wireguard_xaead_encrypt(dst,src,srclen,ad,adlen,nonce,key) xchacha20poly1305_encrypt(dst,src,srclen,ad,adlen,nonce,key)
#define wireguard_xaead_decrypt(dst,src,srclen,ad,adlen,nonce,key) xchacha20poly1305_decrypt(dst,src,srclen,ad,adlen,nonce,key)
// Endian / unaligned helper macros
#define U8C(v) (v##U)
#define U32C(v) (v##U)
#define U8V(v) ((uint8_t)(v) & U8C(0xFF))
#define U32V(v) ((uint32_t)(v) & U32C(0xFFFFFFFF))
#define U8TO32_LITTLE(p) \
(((uint32_t)((p)[0]) ) | \
((uint32_t)((p)[1]) << 8) | \
((uint32_t)((p)[2]) << 16) | \
((uint32_t)((p)[3]) << 24))
#define U8TO64_LITTLE(p) \
(((uint64_t)((p)[0]) ) | \
((uint64_t)((p)[1]) << 8) | \
((uint64_t)((p)[2]) << 16) | \
((uint64_t)((p)[3]) << 24) | \
((uint64_t)((p)[4]) << 32) | \
((uint64_t)((p)[5]) << 40) | \
((uint64_t)((p)[6]) << 48) | \
((uint64_t)((p)[7]) << 56))
#define U16TO8_BIG(p, v) \
do { \
(p)[1] = U8V((v) ); \
(p)[0] = U8V((v) >> 8); \
} while (0)
#define U32TO8_LITTLE(p, v) \
do { \
(p)[0] = U8V((v) ); \
(p)[1] = U8V((v) >> 8); \
(p)[2] = U8V((v) >> 16); \
(p)[3] = U8V((v) >> 24); \
} while (0)
#define U32TO8_BIG(p, v) \
do { \
(p)[3] = U8V((v) ); \
(p)[2] = U8V((v) >> 8); \
(p)[1] = U8V((v) >> 16); \
(p)[0] = U8V((v) >> 24); \
} while (0)
#define U64TO8_LITTLE(p, v) \
do { \
(p)[0] = U8V((v) ); \
(p)[1] = U8V((v) >> 8); \
(p)[2] = U8V((v) >> 16); \
(p)[3] = U8V((v) >> 24); \
(p)[4] = U8V((v) >> 32); \
(p)[5] = U8V((v) >> 40); \
(p)[6] = U8V((v) >> 48); \
(p)[7] = U8V((v) >> 56); \
} while (0)
#define U64TO8_BIG(p, v) \
do { \
(p)[7] = U8V((v) ); \
(p)[6] = U8V((v) >> 8); \
(p)[5] = U8V((v) >> 16); \
(p)[4] = U8V((v) >> 24); \
(p)[3] = U8V((v) >> 32); \
(p)[2] = U8V((v) >> 40); \
(p)[1] = U8V((v) >> 48); \
(p)[0] = U8V((v) >> 56); \
} while (0)
void crypto_zero(void *dest, size_t len);
bool crypto_equal(const void *a, const void *b, size_t size);
#ifdef __cplusplus
}
#endif
#endif /* _CRYPTO_H_ */

View File

@ -0,0 +1,158 @@
// Taken from RFC7693 - https://tools.ietf.org/html/rfc7693
#include <Arduino.h>
#include "blake2s.h"
#include "../../crypto.h"
// Cyclic right rotation.
#ifndef ROTR32
#define ROTR32(x, y) (((x) >> (y)) ^ ((x) << (32 - (y))))
#endif
// Mixing function G.
#define B2S_G(a, b, c, d, x, y) { \
v[a] = v[a] + v[b] + x; \
v[d] = ROTR32(v[d] ^ v[a], 16); \
v[c] = v[c] + v[d]; \
v[b] = ROTR32(v[b] ^ v[c], 12); \
v[a] = v[a] + v[b] + y; \
v[d] = ROTR32(v[d] ^ v[a], 8); \
v[c] = v[c] + v[d]; \
v[b] = ROTR32(v[b] ^ v[c], 7); }
// Initialization Vector.
static const uint32_t blake2s_iv[8] =
{
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
};
// Compression function. "last" flag indicates last block.
static void blake2s_compress(blake2s_ctx *ctx, int last)
{
const uint8_t sigma[10][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }
};
int i;
uint32_t v[16], m[16];
for (i = 0; i < 8; i++) { // init work variables
v[i] = ctx->h[i];
v[i + 8] = blake2s_iv[i];
}
v[12] ^= ctx->t[0]; // low 32 bits of offset
v[13] ^= ctx->t[1]; // high 32 bits
if (last) // last block flag set ?
v[14] = ~v[14];
for (i = 0; i < 16; i++) // get little-endian words
m[i] = U8TO32_LITTLE(&ctx->b[4 * i]);
for (i = 0; i < 10; i++) { // ten rounds
B2S_G( 0, 4, 8, 12, m[sigma[i][ 0]], m[sigma[i][ 1]]);
B2S_G( 1, 5, 9, 13, m[sigma[i][ 2]], m[sigma[i][ 3]]);
B2S_G( 2, 6, 10, 14, m[sigma[i][ 4]], m[sigma[i][ 5]]);
B2S_G( 3, 7, 11, 15, m[sigma[i][ 6]], m[sigma[i][ 7]]);
B2S_G( 0, 5, 10, 15, m[sigma[i][ 8]], m[sigma[i][ 9]]);
B2S_G( 1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]);
B2S_G( 2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]);
B2S_G( 3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]);
}
for( i = 0; i < 8; ++i )
ctx->h[i] ^= v[i] ^ v[i + 8];
}
// Initialize the hashing context "ctx" with optional key "key".
// 1 <= outlen <= 32 gives the digest size in bytes.
// Secret key (also <= 32 bytes) is optional (keylen = 0).
int blake2s_init(blake2s_ctx *ctx, size_t outlen,
const void *key, size_t keylen) // (keylen=0: no key)
{
size_t i;
if (outlen == 0 || outlen > 32 || keylen > 32)
return -1; // illegal parameters
for (i = 0; i < 8; i++) // state, "param block"
ctx->h[i] = blake2s_iv[i];
ctx->h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen;
ctx->t[0] = 0; // input count low word
ctx->t[1] = 0; // input count high word
ctx->c = 0; // pointer within buffer
ctx->outlen = outlen;
for (i = keylen; i < 64; i++) // zero input block
ctx->b[i] = 0;
if (keylen > 0) {
blake2s_update(ctx, key, keylen);
ctx->c = 64; // at the end
}
return 0;
}
// Add "inlen" bytes from "in" into the hash.
void blake2s_update(blake2s_ctx *ctx,
const void *in, size_t inlen) // data bytes
{
size_t i;
for (i = 0; i < inlen; i++) {
if (ctx->c == 64) { // buffer full ?
ctx->t[0] += ctx->c; // add counters
if (ctx->t[0] < ctx->c) // carry overflow ?
ctx->t[1]++; // high word
blake2s_compress(ctx, 0); // compress (not last)
ctx->c = 0; // counter to zero
}
// ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
ctx->b[ctx->c++] = pgm_read_byte(&((const uint8_t *)in)[i]); // PROGMEM compatible
}
}
// Generate the message digest (size given in init).
// Result placed in "out".
void blake2s_final(blake2s_ctx *ctx, void *out)
{
size_t i;
ctx->t[0] += ctx->c; // mark last block offset
if (ctx->t[0] < ctx->c) // carry overflow
ctx->t[1]++; // high word
while (ctx->c < 64) // fill up with zeros
ctx->b[ctx->c++] = 0;
blake2s_compress(ctx, 1); // final block flag = 1
// little endian convert and store
for (i = 0; i < ctx->outlen; i++) {
((uint8_t *) out)[i] =
(ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF;
}
}
// Convenience function for all-in-one computation.
int blake2s(void *out, size_t outlen,
const void *key, size_t keylen,
const void *in, size_t inlen)
{
blake2s_ctx ctx;
if (blake2s_init(&ctx, outlen, key, keylen))
return -1;
blake2s_update(&ctx, in, inlen);
blake2s_final(&ctx, out);
return 0;
}

View File

@ -0,0 +1,47 @@
// Taken from RFC7693 - https://tools.ietf.org/html/rfc7693
// BLAKE2s Hashing Context and API Prototypes
#ifndef _BLAKE2S_H
#define _BLAKE2S_H
#ifdef __cplusplus
extern "C" {
#endif
#define BLAKE2S_BLOCK_SIZE 64
#include <stdint.h>
#include <stddef.h>
// state context
typedef struct {
uint8_t b[64]; // input buffer
uint32_t h[8]; // chained state
uint32_t t[2]; // total number of bytes
size_t c; // pointer for b[]
size_t outlen; // digest size
} blake2s_ctx;
// Initialize the hashing context "ctx" with optional key "key".
// 1 <= outlen <= 32 gives the digest size in bytes.
// Secret key (also <= 32 bytes) is optional (keylen = 0).
int blake2s_init(blake2s_ctx *ctx, size_t outlen,
const void *key, size_t keylen); // secret key
// Add "inlen" bytes from "in" into the hash.
void blake2s_update(blake2s_ctx *ctx, // context
const void *in, size_t inlen); // data to be hashed
// Generate the message digest (size given in init).
// Result placed in "out".
void blake2s_final(blake2s_ctx *ctx, void *out);
// All-in-one convenience function.
int blake2s(void *out, size_t outlen, // return buffer for digest
const void *key, size_t keylen, // optional secret key
const void *in, size_t inlen); // data to be hashed
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,202 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
// RFC7539 implementation of ChaCha20 with modified nonce size for WireGuard
// https://tools.ietf.org/html/rfc7539
// Adapted from https://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c by D. J. Bernstein (Public Domain)
// HChaCha20 is described here: https://tools.ietf.org/id/draft-arciszewski-xchacha-02.html
#include "chacha20.h"
#include <string.h>
#include <stdint.h>
#include "../../crypto.h"
// 2.3. The ChaCha20 Block Function
// The first four words (0-3) are constants: 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
static const uint32_t CHACHA20_CONSTANT_1 = 0x61707865;
static const uint32_t CHACHA20_CONSTANT_2 = 0x3320646e;
static const uint32_t CHACHA20_CONSTANT_3 = 0x79622d32;
static const uint32_t CHACHA20_CONSTANT_4 = 0x6b206574;
#define ROTL32(v, n) (U32V((v) << (n)) | ((v) >> (32 - (n))))
#define PLUS(v,w) (U32V((v) + (w)))
#define PLUSONE(v) (PLUS((v),1))
// 2.1. The ChaCha Quarter Round
// 1. a += b; d ^= a; d <<<= 16;
// 2. c += d; b ^= c; b <<<= 12;
// 3. a += b; d ^= a; d <<<= 8;
// 4. c += d; b ^= c; b <<<= 7;
#define QUARTERROUND(a, b, c, d) \
a += b; d ^= a; d = ROTL32(d, 16); \
c += d; b ^= c; b = ROTL32(b, 12); \
a += b; d ^= a; d = ROTL32(d, 8); \
c += d; b ^= c; b = ROTL32(b, 7)
static inline void INNER_BLOCK(uint32_t *block) {
QUARTERROUND(block[0], block[4], block[ 8], block[12]); // column 0
QUARTERROUND(block[1], block[5], block[ 9], block[13]); // column 1
QUARTERROUND(block[2], block[6], block[10], block[14]); // column 2
QUARTERROUND(block[3], block[7], block[11], block[15]); // column 3
QUARTERROUND(block[0], block[5], block[10], block[15]); // diagonal 1
QUARTERROUND(block[1], block[6], block[11], block[12]); // diagonal 2
QUARTERROUND(block[2], block[7], block[ 8], block[13]); // diagonal 3
QUARTERROUND(block[3], block[4], block[ 9], block[14]); // diagonal 4
}
#define TWENTY_ROUNDS(x) ( \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x), \
INNER_BLOCK(x) \
)
// 2.3. The ChaCha20 Block Function
// chacha20_block(key, counter, nonce):
// state = constants | key | counter | nonce
// working_state = state
// for i=1 upto 10
// inner_block(working_state)
// end
// state += working_state
// return serialize(state)
// end
static void chacha20_block(struct chacha20_ctx *ctx, uint8_t *stream) {
uint32_t working_state[16];
int i;
for (i = 0; i < 16; ++i) {
working_state[i] = ctx->state[i];
}
TWENTY_ROUNDS(working_state);
for (i = 0; i < 16; ++i) {
U32TO8_LITTLE(stream + (4 * i), PLUS(working_state[i], ctx->state[i]));
}
}
void chacha20(struct chacha20_ctx *ctx, uint8_t *out, const uint8_t *in, uint32_t len) {
uint8_t output[CHACHA20_BLOCK_SIZE];
int i;
if (len) {
for (;;) {
chacha20_block(ctx, output);
// Word 12 is a block counter
ctx->state[12] = PLUSONE(ctx->state[12]);
if (len <= 64) {
for (i = 0;i < len;++i) {
out[i] = in[i] ^ output[i];
}
return;
}
for (i = 0;i < 64;++i) {
out[i] = in[i] ^ output[i];
}
len -= 64;
out += 64;
in += 64;
}
}
}
// 2.3. The ChaCha20 Block Function
// The first four words (0-3) are constants: 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
// The next eight words (4-11) are taken from the 256-bit key by reading the bytes in little-endian order, in 4-byte chunks.
// Word 12 is a block counter. Since each block is 64-byte, a 32-bit word is enough for 256 gigabytes of data.
// Words 13-15 are a nonce, which should not be repeated for the same key.
// For wireguard: "nonce being composed of 32 bits of zeros followed by the 64-bit little-endian value of counter." where counter comes from the Wireguard layer and is separate from the block counter in word 12
void chacha20_init(struct chacha20_ctx *ctx, const uint8_t *key, const uint64_t nonce) {
ctx->state[0] = CHACHA20_CONSTANT_1;
ctx->state[1] = CHACHA20_CONSTANT_2;
ctx->state[2] = CHACHA20_CONSTANT_3;
ctx->state[3] = CHACHA20_CONSTANT_4;
ctx->state[4] = U8TO32_LITTLE(key + 0);
ctx->state[5] = U8TO32_LITTLE(key + 4);
ctx->state[6] = U8TO32_LITTLE(key + 8);
ctx->state[7] = U8TO32_LITTLE(key + 12);
ctx->state[8] = U8TO32_LITTLE(key + 16);
ctx->state[9] = U8TO32_LITTLE(key + 20);
ctx->state[10] = U8TO32_LITTLE(key + 24);
ctx->state[11] = U8TO32_LITTLE(key + 28);
ctx->state[12] = 0;
ctx->state[13] = 0;
ctx->state[14] = nonce & 0xFFFFFFFF;
ctx->state[15] = nonce >> 32;
}
// 2.2. HChaCha20
// HChaCha20 is initialized the same way as the ChaCha cipher, except that HChaCha20 uses a 128-bit nonce and has no counter.
// After initialization, proceed through the ChaCha rounds as usual.
// Once the 20 ChaCha rounds have been completed, the first 128 bits and last 128 bits of the ChaCha state (both little-endian) are concatenated, and this 256-bit subkey is returned.
void hchacha20(uint8_t *out, const uint8_t *nonce, const uint8_t *key) {
uint32_t state[16];
state[0] = CHACHA20_CONSTANT_1;
state[1] = CHACHA20_CONSTANT_2;
state[2] = CHACHA20_CONSTANT_3;
state[3] = CHACHA20_CONSTANT_4;
state[4] = U8TO32_LITTLE(key + 0);
state[5] = U8TO32_LITTLE(key + 4);
state[6] = U8TO32_LITTLE(key + 8);
state[7] = U8TO32_LITTLE(key + 12);
state[8] = U8TO32_LITTLE(key + 16);
state[9] = U8TO32_LITTLE(key + 20);
state[10] = U8TO32_LITTLE(key + 24);
state[11] = U8TO32_LITTLE(key + 28);
state[12] = U8TO32_LITTLE(nonce + 0);
state[13] = U8TO32_LITTLE(nonce + 4);
state[14] = U8TO32_LITTLE(nonce + 8);
state[15] = U8TO32_LITTLE(nonce + 12);
TWENTY_ROUNDS(state);
// Concatenate first/last 128 bits into 256bit output (as little endian)
U32TO8_LITTLE(out + 0, state[0]);
U32TO8_LITTLE(out + 4, state[1]);
U32TO8_LITTLE(out + 8, state[2]);
U32TO8_LITTLE(out + 12, state[3]);
U32TO8_LITTLE(out + 16, state[12]);
U32TO8_LITTLE(out + 20, state[13]);
U32TO8_LITTLE(out + 24, state[14]);
U32TO8_LITTLE(out + 28, state[15]);
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
// RFC7539 implementation of ChaCha20 with modified nonce size for WireGuard
// https://tools.ietf.org/html/rfc7539
// Adapted from https://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c by D. J. Bernstein (Public Domain)
// HChaCha20 is described here: https://tools.ietf.org/id/draft-arciszewski-xchacha-02.html
#ifndef _CHACHA20_H_
#define _CHACHA20_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#define CHACHA20_BLOCK_SIZE (64)
#define CHACHA20_KEY_SIZE (32)
struct chacha20_ctx {
uint32_t state[16];
};
void chacha20_init(struct chacha20_ctx *ctx, const uint8_t *key, const uint64_t nonce);
void chacha20(struct chacha20_ctx *ctx, uint8_t *out, const uint8_t *in, uint32_t len);
void hchacha20(uint8_t *out, const uint8_t *nonce, const uint8_t *key);
#ifdef __cplusplus
}
#endif
#endif /* _CHACHA20_H_ */

View File

@ -0,0 +1,193 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
// AEAD_CHACHA20_POLY1305 as described in https://tools.ietf.org/html/rfc7539
// AEAD_XChaCha20_Poly1305 as described in https://tools.ietf.org/id/draft-arciszewski-xchacha-02.html
#include "chacha20poly1305.h"
#include "chacha20.h"
#include "poly1305-donna.h"
#include <stdlib.h>
#include <stdint.h>
#include "../../crypto.h"
#define POLY1305_KEY_SIZE 32
#define POLY1305_MAC_SIZE 16
static const uint8_t zero[CHACHA20_BLOCK_SIZE] = { 0 };
// 2.6. Generating the Poly1305 Key Using ChaCha20
static void generate_poly1305_key(struct poly1305_context *poly1305_state, struct chacha20_ctx *chacha20_state, const uint8_t *key, uint64_t nonce) {
uint8_t block[POLY1305_KEY_SIZE] = {0};
// The method is to call the block function with the following parameters:
// - The 256-bit session integrity key is used as the ChaCha20 key.
// - The block counter is set to zero.
// - The protocol will specify a 96-bit or 64-bit nonce
chacha20_init(chacha20_state, key, nonce);
// We take the first 256 bits or the serialized state, and use those as the one-time Poly1305 key
chacha20(chacha20_state, block, block, sizeof(block));
poly1305_init(poly1305_state, block);
crypto_zero(&block, sizeof(block));
}
// 2.8. AEAD Construction (Encryption)
void chacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, uint64_t nonce, const uint8_t *key) {
struct poly1305_context poly1305_state;
struct chacha20_ctx chacha20_state;
uint8_t block[8];
size_t padded_len;
// First, a Poly1305 one-time key is generated from the 256-bit key and nonce using the procedure described in Section 2.6.
generate_poly1305_key(&poly1305_state, &chacha20_state, key, nonce);
// Next, the ChaCha20 encryption function is called to encrypt the plaintext, using the same key and nonce, and with the initial counter set to 1.
chacha20(&chacha20_state, dst, src, src_len);
// Finally, the Poly1305 function is called with the Poly1305 key calculated above, and a message constructed as a concatenation of the following:
// - The AAD
poly1305_update(&poly1305_state, ad, ad_len);
// - padding1 -- the padding is up to 15 zero bytes, and it brings the total length so far to an integral multiple of 16
padded_len = (ad_len + 15) & 0xFFFFFFF0; // Round up to next 16 bytes
poly1305_update(&poly1305_state, zero, padded_len - ad_len);
// - The ciphertext
poly1305_update(&poly1305_state, dst, src_len);
// - padding2 -- the padding is up to 15 zero bytes, and it brings the total length so far to an integral multiple of 16.
padded_len = (src_len + 15) & 0xFFFFFFF0; // Round up to next 16 bytes
poly1305_update(&poly1305_state, zero, padded_len - src_len);
// - The length of the additional data in octets (as a 64-bit little-endian integer)
U64TO8_LITTLE(block, (uint64_t)ad_len);
poly1305_update(&poly1305_state, block, sizeof(block));
// - The length of the ciphertext in octets (as a 64-bit little-endian integer).
U64TO8_LITTLE(block, (uint64_t)src_len);
poly1305_update(&poly1305_state, block, sizeof(block));
// The output from the AEAD is twofold:
// - A ciphertext of the same length as the plaintext. (above, output of chacha20 into dst)
// - A 128-bit tag, which is the output of the Poly1305 function. (append to dst)
poly1305_finish(&poly1305_state, dst + src_len);
// Make sure we leave nothing sensitive on the stack
crypto_zero(&chacha20_state, sizeof(chacha20_state));
crypto_zero(&block, sizeof(block));
}
// 2.8. AEAD Construction (Decryption)
bool chacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, uint64_t nonce, const uint8_t *key) {
struct poly1305_context poly1305_state;
struct chacha20_ctx chacha20_state;
uint8_t block[8];
uint8_t mac[POLY1305_MAC_SIZE];
size_t padded_len;
int dst_len;
bool result = false;
// Decryption is similar [to encryption] with the following differences:
// - The roles of ciphertext and plaintext are reversed, so the ChaCha20 encryption function is applied to the ciphertext, producing the plaintext.
// - The Poly1305 function is still run on the AAD and the ciphertext, not the plaintext.
// - The calculated tag is bitwise compared to the received tag. The message is authenticated if and only if the tags match.
if (src_len >= POLY1305_MAC_SIZE) {
dst_len = src_len - POLY1305_MAC_SIZE;
// First, a Poly1305 one-time key is generated from the 256-bit key and nonce using the procedure described in Section 2.6.
generate_poly1305_key(&poly1305_state, &chacha20_state, key, nonce);
// Calculate the MAC before attempting decryption
// the Poly1305 function is called with the Poly1305 key calculated above, and a message constructed as a concatenation of the following:
// - The AAD
poly1305_update(&poly1305_state, ad, ad_len);
// - padding1 -- the padding is up to 15 zero bytes, and it brings the total length so far to an integral multiple of 16
padded_len = (ad_len + 15) & 0xFFFFFFF0; // Round up to next 16 bytes
poly1305_update(&poly1305_state, zero, padded_len - ad_len);
// - The ciphertext (note the Poly1305 function is still run on the AAD and the ciphertext, not the plaintext)
poly1305_update(&poly1305_state, src, dst_len);
// - padding2 -- the padding is up to 15 zero bytes, and it brings the total length so far to an integral multiple of 16.
padded_len = (dst_len + 15) & 0xFFFFFFF0; // Round up to next 16 bytes
poly1305_update(&poly1305_state, zero, padded_len - dst_len);
// - The length of the additional data in octets (as a 64-bit little-endian integer)
U64TO8_LITTLE(block, (uint64_t)ad_len);
poly1305_update(&poly1305_state, block, sizeof(block));
// - The length of the ciphertext in octets (as a 64-bit little-endian integer).
U64TO8_LITTLE(block, (uint64_t)dst_len);
poly1305_update(&poly1305_state, block, sizeof(block));
// The output from the AEAD is twofold:
// - A plaintext of the same length as the ciphertext. (below, output of chacha20 into dst)
// - A 128-bit tag, which is the output of the Poly1305 function. (into mac for checking against passed mac)
poly1305_finish(&poly1305_state, mac);
if (crypto_equal(mac, src + dst_len, POLY1305_MAC_SIZE)) {
// mac is correct - do the decryption
// Next, the ChaCha20 encryption function is called to decrypt the ciphertext, using the same key and nonce, and with the initial counter set to 1.
chacha20(&chacha20_state, dst, src, dst_len);
result = true;
}
}
return result;
}
// AEAD_XChaCha20_Poly1305
// XChaCha20-Poly1305 is a variant of the ChaCha20-Poly1305 AEAD construction as defined in [RFC7539] that uses a 192-bit nonce instead of a 96-bit nonce.
// The algorithm for XChaCha20-Poly1305 is as follows:
// 1. Calculate a subkey from the first 16 bytes of the nonce and the key, using HChaCha20 (Section 2.2).
// 2. Use the subkey and remaining 8 bytes of the nonce (prefixed with 4 NUL bytes) with AEAD_CHACHA20_POLY1305 from [RFC7539] as normal. The definition for XChaCha20 is given in Section 2.3.
void xchacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, const uint8_t *nonce, const uint8_t *key) {
uint8_t subkey[CHACHA20_KEY_SIZE];
uint64_t new_nonce;
new_nonce = U8TO64_LITTLE(nonce + 16);
hchacha20(subkey, nonce, key);
chacha20poly1305_encrypt(dst, src, src_len, ad, ad_len, new_nonce, subkey);
crypto_zero(subkey, sizeof(subkey));
}
bool xchacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, const uint8_t *nonce, const uint8_t *key) {
uint8_t subkey[CHACHA20_KEY_SIZE];
uint64_t new_nonce;
bool result;
new_nonce = U8TO64_LITTLE(nonce + 16);
hchacha20(subkey, nonce, key);
result = chacha20poly1305_decrypt(dst, src, src_len, ad, ad_len, new_nonce, subkey);
crypto_zero(subkey, sizeof(subkey));
return result;
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
#ifndef _CHACHA20POLY1305_H_
#define _CHACHA20POLY1305_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
// Aead(key, counter, plain text, auth text) ChaCha20Poly1305 AEAD, as specified in RFC7539 [17], with its nonce being composed of 32 bits of zeros followed by the 64-bit little-endian value of counter.
// AEAD_CHACHA20_POLY1305 as described in https://tools.ietf.org/html/rfc7539
void chacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, uint64_t nonce, const uint8_t *key);
bool chacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, uint64_t nonce, const uint8_t *key);
// Xaead(key, nonce, plain text, auth text) XChaCha20Poly1305 AEAD, with a 24-byte random nonce, instantiated using HChaCha20 [6] and ChaCha20Poly1305.
// AEAD_XChaCha20_Poly1305 as described in https://tools.ietf.org/id/draft-arciszewski-xchacha-02.html
void xchacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, const uint8_t *nonce, const uint8_t *key);
bool xchacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len, const uint8_t *ad, size_t ad_len, const uint8_t *nonce, const uint8_t *key);
#ifdef __cplusplus
}
#endif
#endif /* _CHACHA20POLY1305_H_ */

View File

@ -0,0 +1,227 @@
// Taken from https://github.com/floodyberry/poly1305-donna - public domain or MIT
/*
poly1305 implementation using 32 bit * 32 bit = 64 bit multiplication and 64 bit addition
*/
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_MSC_VER)
#define POLY1305_NOINLINE __declspec(noinline)
#elif defined(__GNUC__)
#define POLY1305_NOINLINE __attribute__((noinline))
#else
#define POLY1305_NOINLINE
#endif
#define poly1305_block_size 16
/* 17 + sizeof(size_t) + 14*sizeof(unsigned long) */
typedef struct poly1305_state_internal_t {
unsigned long r[5];
unsigned long h[5];
unsigned long pad[4];
size_t leftover;
unsigned char buffer[poly1305_block_size];
unsigned char final;
} poly1305_state_internal_t;
/* interpret four 8 bit unsigned integers as a 32 bit unsigned integer in little endian */
static unsigned long
U8TO32(const unsigned char *p) {
return
(((unsigned long)(p[0] & 0xff) ) |
((unsigned long)(p[1] & 0xff) << 8) |
((unsigned long)(p[2] & 0xff) << 16) |
((unsigned long)(p[3] & 0xff) << 24));
}
/* store a 32 bit unsigned integer as four 8 bit unsigned integers in little endian */
static void
U32TO8(unsigned char *p, unsigned long v) {
p[0] = (v ) & 0xff;
p[1] = (v >> 8) & 0xff;
p[2] = (v >> 16) & 0xff;
p[3] = (v >> 24) & 0xff;
}
void
poly1305_init(poly1305_context *ctx, const unsigned char key[32]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
st->r[0] = (U8TO32(&key[ 0]) ) & 0x3ffffff;
st->r[1] = (U8TO32(&key[ 3]) >> 2) & 0x3ffff03;
st->r[2] = (U8TO32(&key[ 6]) >> 4) & 0x3ffc0ff;
st->r[3] = (U8TO32(&key[ 9]) >> 6) & 0x3f03fff;
st->r[4] = (U8TO32(&key[12]) >> 8) & 0x00fffff;
/* h = 0 */
st->h[0] = 0;
st->h[1] = 0;
st->h[2] = 0;
st->h[3] = 0;
st->h[4] = 0;
/* save pad for later */
st->pad[0] = U8TO32(&key[16]);
st->pad[1] = U8TO32(&key[20]);
st->pad[2] = U8TO32(&key[24]);
st->pad[3] = U8TO32(&key[28]);
st->leftover = 0;
st->final = 0;
}
static void
poly1305_blocks(poly1305_state_internal_t *st, const unsigned char *m, size_t bytes) {
const unsigned long hibit = (st->final) ? 0 : (1UL << 24); /* 1 << 128 */
unsigned long r0,r1,r2,r3,r4;
unsigned long s1,s2,s3,s4;
unsigned long h0,h1,h2,h3,h4;
unsigned long long d0,d1,d2,d3,d4;
unsigned long c;
r0 = st->r[0];
r1 = st->r[1];
r2 = st->r[2];
r3 = st->r[3];
r4 = st->r[4];
s1 = r1 * 5;
s2 = r2 * 5;
s3 = r3 * 5;
s4 = r4 * 5;
h0 = st->h[0];
h1 = st->h[1];
h2 = st->h[2];
h3 = st->h[3];
h4 = st->h[4];
while (bytes >= poly1305_block_size) {
/* h += m[i] */
h0 += (U8TO32(m+ 0) ) & 0x3ffffff;
h1 += (U8TO32(m+ 3) >> 2) & 0x3ffffff;
h2 += (U8TO32(m+ 6) >> 4) & 0x3ffffff;
h3 += (U8TO32(m+ 9) >> 6) & 0x3ffffff;
h4 += (U8TO32(m+12) >> 8) | hibit;
/* h *= r */
d0 = ((unsigned long long)h0 * r0) + ((unsigned long long)h1 * s4) + ((unsigned long long)h2 * s3) + ((unsigned long long)h3 * s2) + ((unsigned long long)h4 * s1);
d1 = ((unsigned long long)h0 * r1) + ((unsigned long long)h1 * r0) + ((unsigned long long)h2 * s4) + ((unsigned long long)h3 * s3) + ((unsigned long long)h4 * s2);
d2 = ((unsigned long long)h0 * r2) + ((unsigned long long)h1 * r1) + ((unsigned long long)h2 * r0) + ((unsigned long long)h3 * s4) + ((unsigned long long)h4 * s3);
d3 = ((unsigned long long)h0 * r3) + ((unsigned long long)h1 * r2) + ((unsigned long long)h2 * r1) + ((unsigned long long)h3 * r0) + ((unsigned long long)h4 * s4);
d4 = ((unsigned long long)h0 * r4) + ((unsigned long long)h1 * r3) + ((unsigned long long)h2 * r2) + ((unsigned long long)h3 * r1) + ((unsigned long long)h4 * r0);
/* (partial) h %= p */
c = (unsigned long)(d0 >> 26); h0 = (unsigned long)d0 & 0x3ffffff;
d1 += c; c = (unsigned long)(d1 >> 26); h1 = (unsigned long)d1 & 0x3ffffff;
d2 += c; c = (unsigned long)(d2 >> 26); h2 = (unsigned long)d2 & 0x3ffffff;
d3 += c; c = (unsigned long)(d3 >> 26); h3 = (unsigned long)d3 & 0x3ffffff;
d4 += c; c = (unsigned long)(d4 >> 26); h4 = (unsigned long)d4 & 0x3ffffff;
h0 += c * 5; c = (h0 >> 26); h0 = h0 & 0x3ffffff;
h1 += c;
m += poly1305_block_size;
bytes -= poly1305_block_size;
}
st->h[0] = h0;
st->h[1] = h1;
st->h[2] = h2;
st->h[3] = h3;
st->h[4] = h4;
}
POLY1305_NOINLINE void
poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
unsigned long h0,h1,h2,h3,h4,c;
unsigned long g0,g1,g2,g3,g4;
unsigned long long f;
unsigned long mask;
/* process the remaining block */
if (st->leftover) {
size_t i = st->leftover;
st->buffer[i++] = 1;
for (; i < poly1305_block_size; i++)
st->buffer[i] = 0;
st->final = 1;
poly1305_blocks(st, st->buffer, poly1305_block_size);
}
/* fully carry h */
h0 = st->h[0];
h1 = st->h[1];
h2 = st->h[2];
h3 = st->h[3];
h4 = st->h[4];
c = h1 >> 26; h1 = h1 & 0x3ffffff;
h2 += c; c = h2 >> 26; h2 = h2 & 0x3ffffff;
h3 += c; c = h3 >> 26; h3 = h3 & 0x3ffffff;
h4 += c; c = h4 >> 26; h4 = h4 & 0x3ffffff;
h0 += c * 5; c = h0 >> 26; h0 = h0 & 0x3ffffff;
h1 += c;
/* compute h + -p */
g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff;
g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff;
g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff;
g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff;
g4 = h4 + c - (1UL << 26);
/* select h if h < p, or h + -p if h >= p */
mask = (g4 >> ((sizeof(unsigned long) * 8) - 1)) - 1;
g0 &= mask;
g1 &= mask;
g2 &= mask;
g3 &= mask;
g4 &= mask;
mask = ~mask;
h0 = (h0 & mask) | g0;
h1 = (h1 & mask) | g1;
h2 = (h2 & mask) | g2;
h3 = (h3 & mask) | g3;
h4 = (h4 & mask) | g4;
/* h = h % (2^128) */
h0 = ((h0 ) | (h1 << 26)) & 0xffffffff;
h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff;
h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff;
h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff;
/* mac = (h + pad) % (2^128) */
f = (unsigned long long)h0 + st->pad[0] ; h0 = (unsigned long)f;
f = (unsigned long long)h1 + st->pad[1] + (f >> 32); h1 = (unsigned long)f;
f = (unsigned long long)h2 + st->pad[2] + (f >> 32); h2 = (unsigned long)f;
f = (unsigned long long)h3 + st->pad[3] + (f >> 32); h3 = (unsigned long)f;
U32TO8(mac + 0, h0);
U32TO8(mac + 4, h1);
U32TO8(mac + 8, h2);
U32TO8(mac + 12, h3);
/* zero out the state */
st->h[0] = 0;
st->h[1] = 0;
st->h[2] = 0;
st->h[3] = 0;
st->h[4] = 0;
st->r[0] = 0;
st->r[1] = 0;
st->r[2] = 0;
st->r[3] = 0;
st->r[4] = 0;
st->pad[0] = 0;
st->pad[1] = 0;
st->pad[2] = 0;
st->pad[3] = 0;
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,41 @@
// Taken from https://github.com/floodyberry/poly1305-donna - public domain or MIT
#include "poly1305-donna.h"
#include "poly1305-donna-32.h"
void
poly1305_update(poly1305_context *ctx, const unsigned char *m, size_t bytes) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
size_t i;
/* handle leftover */
if (st->leftover) {
size_t want = (poly1305_block_size - st->leftover);
if (want > bytes)
want = bytes;
for (i = 0; i < want; i++)
st->buffer[st->leftover + i] = m[i];
bytes -= want;
m += want;
st->leftover += want;
if (st->leftover < poly1305_block_size)
return;
poly1305_blocks(st, st->buffer, poly1305_block_size);
st->leftover = 0;
}
/* process full blocks */
if (bytes >= poly1305_block_size) {
size_t want = (bytes & ~(poly1305_block_size - 1));
poly1305_blocks(st, m, want);
m += want;
bytes -= want;
}
/* store leftover */
if (bytes) {
for (i = 0; i < bytes; i++)
st->buffer[st->leftover + i] = m[i];
st->leftover += bytes;
}
}

View File

@ -0,0 +1,24 @@
// Taken from https://github.com/floodyberry/poly1305-donna - public domain or MIT
#ifndef POLY1305_DONNA_H
#define POLY1305_DONNA_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
typedef struct poly1305_context {
size_t aligner;
unsigned char opaque[136];
} poly1305_context;
void poly1305_init(poly1305_context *ctx, const unsigned char key[32]);
void poly1305_update(poly1305_context *ctx, const unsigned char *m, size_t bytes);
void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]);
#ifdef __cplusplus
}
#endif
#endif /* POLY1305_DONNA_H */

View File

@ -0,0 +1,259 @@
/*
* Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
* Copyright (c) 2023-2024 Simone Rossetto <simros85@gmail.com>
* Copyright (c) 2025 Stephan Hadinger
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
#include <Arduino.h>
#include "esp_wireguard.h"
#include "WiFiHelper.h"
#include "lwip/ip.h"
#include "lwip/ip_addr.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
#include "lwip/err.h"
#include "wireguard-platform.h"
#include "wireguardif.h"
//**************************************************************************************************************
// enable AddLog support within a C++ library
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
//**************************************************************************************************************
#if defined(CONFIG_LWIP_IPV6)
#define WG_ADDRSTRLEN INET6_ADDRSTRLEN
#else
#define WG_ADDRSTRLEN INET_ADDRSTRLEN
#endif
static struct netif wg_netif_struct = {0};
static struct netif *wg_netif = NULL;
static wireguardif_peer_t peer = {0};
static uint8_t wireguard_peer_index = WIREGUARDIF_INVALID_INDEX;
static esp_err_t esp_wireguard_peer_init(const wireguard_config_t *config, wireguardif_peer_t *peer)
{
if (!config || !peer) { return ESP_ERR_INVALID_ARG; }
if (ip_addr_isany(&(config->endpoint_ip))) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : peer_init: invalid endpoint ip: `%s`"), ipaddr_ntoa(&(config->endpoint_ip)));
return ESP_ERR_INVALID_ARG;
}
ip_addr_copy(peer->endpoint_ip, config->endpoint_ip);
memmove(peer->public_key2, config->public_key2, WG_KEY_LEN);
memmove(peer->preshared_key2, config->preshared_key2, WG_KEY_LEN);
peer->keep_alive = config->persistent_keepalive;
/* Allow device's own address through tunnel */
peer->allowed_ip = config->address2;
peer->allowed_mask = config->subnet;
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : Default allowed_ips %s/%s"), IPAddress(&peer->allowed_ip).toString().c_str(),
IPAddress(&peer->allowed_mask).toString().c_str());
peer->endport_port = config->port;
peer->keep_alive = config->persistent_keepalive;
return ESP_OK;
}
static esp_err_t esp_wireguard_netif_create(const wireguard_config_t *config)
{
esp_err_t err;
ip_addr_t gateway = IPADDR4_INIT_BYTES(0, 0, 0, 0);
struct wireguardif_init_data wg = {0};
if (!config) { return ESP_ERR_INVALID_ARG;}
/* Setup the WireGuard device structure */
memmove(wg.private_key2, config->private_key2, WG_KEY_LEN);
wg.listen_port = config->listen_port;
wg.bind_netif = NULL;
/* Register the new WireGuard network interface with lwIP */
// AddLog(LOG_LEVEL_DEBUG, "WG : Creating netif addr %_I mask %_I gateway %_I",
// config->address2.u_addr.ip4.addr, config->netmask2.u_addr.ip4.addr, gateway.u_addr.ip4.addr);
wg_netif = netif_add(
&wg_netif_struct,
ip_2_ip4(&config->address2),
ip_2_ip4(&config->netmask2),
ip_2_ip4(&gateway),
&wg, &wireguardif_init,
&ip_input);
if (wg_netif == NULL) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : netif_add: failed"));
return ESP_FAIL;
}
/* Mark the interface as administratively up, link up flag is set
* automatically when peer connects */
netif_set_up(wg_netif);
return ESP_OK;
}
esp_err_t esp_wireguard_init(wireguard_config_t *config, wireguard_ctx_t *ctx)
{
if (!config || !ctx) { return ESP_ERR_INVALID_ARG; }
ctx->config = config;
ctx->netif = NULL;
return ESP_OK;
}
esp_err_t esp_wireguard_connect(wireguard_ctx_t *ctx)
{
esp_err_t err = ESP_FAIL;
err_t lwip_err = -1;
IPAddress remote_addr;
if (!ctx) {
err = ESP_ERR_INVALID_ARG;
goto fail;
}
if (ctx->netif == NULL) {
err = esp_wireguard_netif_create(ctx->config);
if (err != ESP_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : netif_create err %d"), err);
goto fail;
}
ctx->netif = wg_netif;
// ctx->netif_default = netif_default;
}
// Add include "ESP8266WiFi.h" for this to work
if (!WiFiHelper::hostByName(ctx->config->endpoint, remote_addr)) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : hostByName failed '%s'"), ctx->config->endpoint);
goto fail;
}
#ifdef ESP32
remote_addr.to_ip_addr_t(&ctx->config->endpoint_ip);
#else
ctx->config->endpoint_ip = remote_addr;
#endif
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("WG : hostByName '%s' resolved to %s"), ctx->config->endpoint, remote_addr.toString().c_str());
/* Initialize the first WireGuard peer structure */
err = esp_wireguard_peer_init(ctx->config, &peer);
if (err != ESP_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : peer_init: %d"), err);
goto fail;
}
/* Register the new WireGuard peer with the network interface */
lwip_err = wireguardif_add_peer(ctx->netif, &peer, &wireguard_peer_index);
if (lwip_err != ERR_OK || wireguard_peer_index == WIREGUARDIF_INVALID_INDEX) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : wireguardif_add_peer: %i"), lwip_err);
err = ESP_FAIL;
goto fail;
}
if (ip_addr_isany(&peer.endpoint_ip)) {
err = ESP_FAIL;
goto fail;
}
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : Connecting to %s (%s) port %i"), ctx->config->endpoint, ipaddr_ntoa(&(peer.endpoint_ip)), peer.endport_port);
lwip_err = wireguardif_connect(ctx->netif, wireguard_peer_index);
if (lwip_err != ERR_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : wireguardif_connect: %i"), lwip_err);
err = ESP_FAIL;
goto fail;
}
err = ESP_OK;
fail:
// AddLog(LOG_LEVEL_DEBUG, ">>>: OUT esp_wireguard_connect %d", err);
return err;
}
esp_err_t esp_wireguard_disconnect(wireguard_ctx_t *ctx)
{
err_t lwip_err;
if (!ctx || !ctx->netif) { return ESP_ERR_INVALID_ARG; }
// Clear the IP address to gracefully disconnect any clients while the
// peers are still valid
netif_set_ipaddr(ctx->netif, IP4_ADDR_ANY4);
lwip_err = wireguardif_disconnect(ctx->netif, wireguard_peer_index);
if (lwip_err != ERR_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : wireguardif_disconnect: peer_index: %" PRIu8 " err: %i"), wireguard_peer_index, lwip_err);
}
lwip_err = wireguardif_remove_peer(ctx->netif, wireguard_peer_index);
if (lwip_err != ERR_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : wireguardif_remove_peer: peer_index: %" PRIu8 " err: %i"), wireguard_peer_index, lwip_err);
}
wireguard_peer_index = WIREGUARDIF_INVALID_INDEX;
wireguardif_shutdown(ctx->netif);
netif_remove(ctx->netif);
wireguardif_fini(ctx->netif);
ctx->netif = NULL;
return ESP_OK;
}
esp_err_t esp_wireguard_peer_is_up(const wireguard_ctx_t *ctx)
{
err_t lwip_err;
if (!ctx || !ctx->netif) { return ESP_ERR_INVALID_ARG; }
lwip_err = wireguardif_peer_is_up(
ctx->netif,
wireguard_peer_index,
&peer.endpoint_ip,
&peer.endport_port);
return (lwip_err != ERR_OK) ? ESP_FAIL : ESP_OK;
}
esp_err_t esp_wireguard_latest_handshake(const wireguard_ctx_t *ctx, time_t *result)
{
if (!ctx || !ctx->netif) { return ESP_ERR_INVALID_ARG; }
*result = wireguardif_latest_handshake(ctx->netif, wireguard_peer_index);
return (*result > 0) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_wireguard_add_allowed_ip(const wireguard_ctx_t *ctx, const ip_addr_t& allowed_ip, const ip_addr_t& allowed_ip_mask)
{
err_t lwip_err;
if (!ctx || !ctx->netif) { return ESP_ERR_INVALID_ARG; }
// AddLog(LOG_LEVEL_DEBUG, "WG : Added allowed_ips %_I/%_I", allowed_ip.u_addr.ip4.addr, allowed_ip_mask.u_addr.ip4.addr);
lwip_err = wireguardif_add_allowed_ip(ctx->netif, wireguard_peer_index, allowed_ip, allowed_ip_mask);
return (lwip_err == ERR_OK ? ESP_OK : ESP_FAIL);
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 Simone Rossetto <simros85@gmail.com>
*
* 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. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT HOLDER OR 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.
*/
#if !defined(__ESP_WIREGUARD_ERR__H__)
#define __ESP_WIREGUARD_ERR__H__
#if defined(ESP8266)
typedef int esp_err_t;
#define ESP_OK 0 /*!< esp_err_t value indicating success (no error) */
#define ESP_FAIL -1 /*!< Generic esp_err_t code indicating failure */
#define ESP_ERR_NO_MEM 0x101 /*!< Out of memory */
#define ESP_ERR_INVALID_ARG 0x102 /*!< Invalid argument */
#define ESP_ERR_INVALID_STATE 0x103 /*!< Invalid state */
#define ESP_ERR_INVALID_SIZE 0x104 /*!< Invalid size */
#define ESP_ERR_NOT_FOUND 0x105 /*!< Requested resource not found */
#define ESP_ERR_NOT_SUPPORTED 0x106 /*!< Operation or feature not supported */
#define ESP_ERR_TIMEOUT 0x107 /*!< Operation timed out */
#define ESP_ERR_INVALID_RESPONSE 0x108 /*!< Received response was invalid */
#define ESP_ERR_INVALID_CRC 0x109 /*!< CRC or checksum was invalid */
#define ESP_ERR_INVALID_VERSION 0x10A /*!< Version was invalid */
#define ESP_ERR_INVALID_MAC 0x10B /*!< MAC address was invalid */
#define ESP_ERR_NOT_FINISHED 0x10C /*!< There are items remained to retrieve */
#define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */
#define ESP_ERR_MESH_BASE 0x4000 /*!< Starting number of MESH error codes */
#define ESP_ERR_FLASH_BASE 0x6000 /*!< Starting number of flash error codes */
#define ESP_ERR_HW_CRYPTO_BASE 0xc000 /*!< Starting number of HW cryptography module error codes */
#define ESP_ERR_MEMPROT_BASE 0xd000 /*!< Starting number of Memory Protection API error codes */
#else
#include <esp_err.h>
#endif
#endif // __ESP_WIREGUARD_ERR__H__

View File

@ -0,0 +1,83 @@
/*
tasmota_crypto.cpp - crypto layer to call bearssl from wireguard
Copyright (C) 2025 Theo Arends, Stephan Hadinger
This program 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.
This program 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 should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include <t_bearssl.h>
#ifdef ESP32
#include "freertos/task.h"
#include "freertos/FreeRTOS.h"
#include "esp_expression_with_stack.h"
#endif // ESP32
//**************************************************************************************************************
// enable AddLog support within a C++ library
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
//**************************************************************************************************************
#define BR_EC25519_IMPL br_ec_c25519_m15 // BearSSL implementation for Curve 25519
// we need a global
static struct dh_curve25519_t {
uint8_t q[32];
uint8_t n[32];
int result;
} g_dh_curve25519;
static void run_crypto_scalarmult_curve25519(void) {
g_dh_curve25519.result = BR_EC25519_IMPL.mul(g_dh_curve25519.q, 32, g_dh_curve25519.n, 32, BR_EC_curve25519);
}
extern "C" int crypto_scalarmult_curve25519(unsigned char *q, const unsigned char *n,const unsigned char *p) {
for (int32_t i=0; i<32; i++) {
g_dh_curve25519.q[i] = p[i];
g_dh_curve25519.n[i] = n[31-i];
}
#ifdef ESP32
const char *taskName = pcTaskGetName(NULL);
if (uxTaskGetStackHighWaterMark(nullptr) < 2000) {
//Allocate a stack buffer, from heap or as a static form:
StackType_t *shared_stack = (StackType_t*) heap_caps_malloc(4096 * sizeof(StackType_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
//Allocate a mutex to protect its usage:
SemaphoreHandle_t lock = xSemaphoreCreateMutex();
//Call the desired function using the macro helper:
esp_execute_shared_stack_function(lock,
shared_stack,
4096,
run_crypto_scalarmult_curve25519);
vSemaphoreDelete(lock);
free(shared_stack);
} else {
run_crypto_scalarmult_curve25519();
}
#else
run_crypto_scalarmult_curve25519();
#endif
if (g_dh_curve25519.result) {
for (int32_t i=0; i<32; i++) {
q[i] = g_dh_curve25519.q[i];
}
return 0; // Success
} else {
return 1; // Failure
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org>
* Copyright (c) 2023-2024 Simone Rossetto <simros85@gmail.com>
* Copyright (c) 2025 Stephan Hadinger
*
* 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. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT HOLDER OR 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.
*/
#include "wireguard-platform.h"
#include "lwip/sys.h"
#include "crypto.h"
#define ENTROPY_MINIMUM_REQUIRED_THRESHOLD (134)
#define ENTROPY_FUNCTION_DATA NULL
#define ENTROPY_CUSTOM_DATA NULL
#define ENTROPY_CUSTOM_DATA_LENGTH (0)
extern "C" uint32_t phy_get_rand(void); // From the ESP8266 SDK
void wireguard_random_bytes(void *bytes, size_t size) {
#ifdef ESP32
esp_fill_random(bytes, size);
#elif defined(ESP8266)
for (int32_t i=0; i<size; i++) {
*((uint8_t*)bytes + i) = phy_get_rand();
}
#endif
}
uint32_t wireguard_sys_now() {
// Default to the LwIP system time
return sys_now();
}
void wireguard_tai64n_now(uint8_t *output) {
// See https://cr.yp.to/libtai/tai64.html
// 64 bit seconds from 1970 = 8 bytes
// 32 bit nano seconds from current second
struct timeval tv;
gettimeofday(&tv, NULL);
uint64_t seconds = 0x400000000000000aULL + tv.tv_sec;
uint32_t nanos = tv.tv_usec * 1000;
U64TO8_BIG(output + 0, seconds);
U32TO8_BIG(output + 8, nanos);
}
bool wireguard_is_under_load() { // actually not called, we have inlined the fact that it's always false
return false;
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* Copyright (c) 2024 Simone Rossetto <simros85@gmail.com>
* Copyright (c) 2025 Stephan Hadinger
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
#ifndef _WIREGUARD_PLATFORM_H_
#define _WIREGUARD_PLATFORM_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#define WIREGUARD_MAX_PEERS (1)
#define WIREGUARD_MAX_SRC_IPS (4) // TASMOTA
// Per device limit on accepting (valid) initiation requests - per peer
#define MAX_INITIATIONS_PER_SECOND (2)
// The number of milliseconds since system boot - for LwIP systems this could be sys_now()
uint32_t wireguard_sys_now();
// Fill the supplied buffer with random data - random data is used for generating new session keys periodically
void wireguard_random_bytes(void *bytes, size_t size);
// Get the current time in tai64n format - 8 byte seconds, 4 byte nano sub-second - see https://cr.yp.to/libtai/tai64.html for details
// Output buffer passed is 12 bytes
// The Wireguard implementation doesn't strictly need this to be a time, but instead an increasing value
// The remote end of the Wireguard tunnel will use this value in handshake replay detection
void wireguard_tai64n_now(uint8_t *output);
// Is the system under load - i.e. should we generate cookie reply message in response to initiation messages
bool wireguard_is_under_load();
#ifdef __cplusplus
}
#endif
#endif /* _WIREGUARD_PLATFORM_H_ */

View File

@ -0,0 +1,997 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* Copyright (c) 2024 Simone Rossetto <simros85@gmail.com>
* Copyright (c) 2025 Stephan Hadinger
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
#include <Arduino.h>
#include "wireguard.h"
#include "crypto.h"
// For HMAC calculation
#define WIREGUARD_BLAKE2S_BLOCK_SIZE (64)
//**************************************************************************************************************
// enable AddLog support within a C++ library
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
//**************************************************************************************************************
// 5.4 Messages
// Constants
static const uint8_t PROGMEM CONSTRUCTION[] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"; // The UTF-8 string literal "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s", 37 bytes of output
static const uint8_t PROGMEM IDENTIFIER[] = "WireGuard v1 zx2c4 Jason@zx2c4.com"; // The UTF-8 string literal "WireGuard v1 zx2c4 Jason@zx2c4.com", 34 bytes of output
static const uint8_t PROGMEM LABEL_MAC1[] = "mac1----"; // Label-Mac1 The UTF-8 string literal "mac1----", 8 bytes of output.
static const uint8_t PROGMEM LABEL_COOKIE[] = "cookie--"; // Label-Cookie The UTF-8 string literal "cookie--", 8 bytes of output
static const uint8_t zero_key[WIREGUARD_PUBLIC_KEY_LEN] = { 0 };
// Calculated in wireguard_init
static uint8_t construction_hash[WIREGUARD_HASH_LEN];
static uint8_t identifier_hash[WIREGUARD_HASH_LEN];
void wireguard_init() {
wireguard_blake2s_ctx ctx;
// Pre-calculate chaining key hash
wireguard_blake2s_init(&ctx, WIREGUARD_HASH_LEN, NULL, 0);
wireguard_blake2s_update(&ctx, CONSTRUCTION, sizeof(CONSTRUCTION) - 1); // remove terminating NULL char
wireguard_blake2s_final(&ctx, construction_hash);
// Pre-calculate initial handshake hash - uses construction_hash calculated above
wireguard_blake2s_init(&ctx, WIREGUARD_HASH_LEN, NULL, 0);
wireguard_blake2s_update(&ctx, construction_hash, sizeof(construction_hash));
wireguard_blake2s_update(&ctx, IDENTIFIER, sizeof(IDENTIFIER) - 1); // remove terminating NULL char
wireguard_blake2s_final(&ctx, identifier_hash);
}
wireguard_peer_t *wireguard_peer_alloc(wireguard_device_t *device) {
for (uint32_t x=0; x < WIREGUARD_MAX_PEERS; x++) {
if (!device->peers[x].valid) {
return &device->peers[x];
}
}
return NULL;
}
wireguard_peer_t *wireguard_peer_lookup_by_pubkey(struct wireguard_device *device, uint8_t *public_key) {
wireguard_peer_t *result = NULL;
wireguard_peer_t *tmp;
for (int32_t x=0; x < WIREGUARD_MAX_PEERS; x++) {
if (device->peers[x].valid) {
if (memcmp(device->peers[x].public_key, public_key, WIREGUARD_PUBLIC_KEY_LEN) == 0) {
return &device->peers[x];
}
}
}
return NULL;
}
uint8_t wireguard_peer_index(struct wireguard_device *device, wireguard_peer_t *peer) {
for (int32_t x=0; x < WIREGUARD_MAX_PEERS; x++) {
if (peer == &device->peers[x]) {
return x;
}
}
return 0xFF; // not found
}
wireguard_peer_t *wireguard_peer_lookup_by_peer_index(struct wireguard_device *device, uint8_t peer_index) {
if (peer_index < WIREGUARD_MAX_PEERS) {
if (device->peers[peer_index].valid) {
return &device->peers[peer_index];
}
}
return NULL;
}
wireguard_peer_t *wireguard_peer_lookup_by_receiver(struct wireguard_device *device, uint32_t receiver) {
for (int32_t x=0; x < WIREGUARD_MAX_PEERS; x++) {
wireguard_peer_t *cur_key = &device->peers[x];
if (cur_key->valid) {
if ((cur_key->curr_keypair.valid && (cur_key->curr_keypair.local_index == receiver)) ||
(cur_key->next_keypair.valid && (cur_key->next_keypair.local_index == receiver)) ||
(cur_key->prev_keypair.valid && (cur_key->prev_keypair.local_index == receiver))
) {
return cur_key;
}
}
}
return NULL;
}
wireguard_peer_t *wireguard_peer_lookup_by_handshake(struct wireguard_device *device, uint32_t receiver) {
for (int32_t x=0; x < WIREGUARD_MAX_PEERS; x++) {
wireguard_peer_t *cur_key = &device->peers[x];
if (cur_key->valid) {
if (cur_key->handshake.valid && cur_key->handshake.initiator && (cur_key->handshake.local_index == receiver)) {
return cur_key;
}
}
}
return NULL;
}
bool wireguard_expired(uint32_t created_millis, uint32_t valid_seconds) {
uint32_t diff = wireguard_sys_now() - created_millis;
return (diff >= (valid_seconds * 1000));
}
static void generate_cookie_secret(struct wireguard_device *device) {
wireguard_random_bytes(device->cookie_secret, WIREGUARD_HASH_LEN);
device->cookie_secret_millis = wireguard_sys_now();
}
static void generate_peer_cookie(struct wireguard_device *device, uint8_t *cookie, uint8_t *source_addr_port, size_t source_length) {
wireguard_blake2s_ctx ctx;
if (wireguard_expired(device->cookie_secret_millis, COOKIE_SECRET_MAX_AGE)) {
// Generate new random bytes
generate_cookie_secret(device);
}
// Mac(key, input) Keyed-Blake2s(key, input, 16), the keyed MAC variant of the BLAKE2s hash function, returning 16 bytes of output
wireguard_blake2s_init(&ctx, WIREGUARD_COOKIE_LEN, device->cookie_secret, WIREGUARD_HASH_LEN);
// 5.4.7 Under Load: Cookie Reply Message
// Mix in the IP address and port - have the IP layer pass this in as byte array to avoid using Lwip specific APIs in this module
if ((source_addr_port) && (source_length > 0)) {
wireguard_blake2s_update(&ctx, source_addr_port, source_length);
}
wireguard_blake2s_final(&ctx, cookie);
}
static void wireguard_mac(uint8_t *dst, const void *message, size_t len, const uint8_t *key, size_t keylen) {
wireguard_blake2s(dst, WIREGUARD_COOKIE_LEN, key, keylen, message, len);
}
static void wireguard_mac_key(uint8_t *key, const uint8_t *public_key, const uint8_t *label, size_t label_len) {
blake2s_ctx ctx;
blake2s_init(&ctx, WIREGUARD_SESSION_KEY_LEN, NULL, 0);
blake2s_update(&ctx, label, label_len);
blake2s_update(&ctx, public_key, WIREGUARD_PUBLIC_KEY_LEN);
blake2s_final(&ctx, key);
}
static void wireguard_mix_hash(uint8_t *hash, const uint8_t *src, size_t src_len) {
wireguard_blake2s_ctx ctx;
wireguard_blake2s_init(&ctx, WIREGUARD_HASH_LEN, NULL, 0);
wireguard_blake2s_update(&ctx, hash, WIREGUARD_HASH_LEN);
wireguard_blake2s_update(&ctx, src, src_len);
wireguard_blake2s_final(&ctx, hash);
}
static void wireguard_hmac(uint8_t *digest, const uint8_t *key, size_t key_len, const uint8_t *text, size_t text_len) {
// Adapted from appendix example in RFC2104 to use BLAKE2S instead of MD5 - https://tools.ietf.org/html/rfc2104
wireguard_blake2s_ctx ctx;
uint8_t k_ipad[WIREGUARD_BLAKE2S_BLOCK_SIZE]; // inner padding - key XORd with ipad
uint8_t k_opad[WIREGUARD_BLAKE2S_BLOCK_SIZE]; // outer padding - key XORd with opad
uint8_t tk[WIREGUARD_HASH_LEN];
int i;
// if key is longer than BLAKE2S_BLOCK_SIZE bytes reset it to key=BLAKE2S(key)
if (key_len > WIREGUARD_BLAKE2S_BLOCK_SIZE) {
wireguard_blake2s_ctx tctx;
wireguard_blake2s_init(&tctx, WIREGUARD_HASH_LEN, NULL, 0);
wireguard_blake2s_update(&tctx, key, key_len);
wireguard_blake2s_final(&tctx, tk);
key = tk;
key_len = WIREGUARD_HASH_LEN;
}
// the HMAC transform looks like:
// HASH(K XOR opad, HASH(K XOR ipad, text))
// where K is an n byte key
// ipad is the byte 0x36 repeated BLAKE2S_BLOCK_SIZE times
// opad is the byte 0x5c repeated BLAKE2S_BLOCK_SIZE times
// and text is the data being protected
memset(k_ipad, 0, sizeof(k_ipad));
memset(k_opad, 0, sizeof(k_opad));
memcpy(k_ipad, key, key_len);
memcpy(k_opad, key, key_len);
// XOR key with ipad and opad values
for (i=0; i < WIREGUARD_BLAKE2S_BLOCK_SIZE; i++) {
k_ipad[i] ^= 0x36;
k_opad[i] ^= 0x5c;
}
// perform inner HASH
wireguard_blake2s_init(&ctx, WIREGUARD_HASH_LEN, NULL, 0); // init context for 1st pass
wireguard_blake2s_update(&ctx, k_ipad, WIREGUARD_BLAKE2S_BLOCK_SIZE); // start with inner pad
wireguard_blake2s_update(&ctx, text, text_len); // then text of datagram
wireguard_blake2s_final(&ctx, digest); // finish up 1st pass
// perform outer HASH
wireguard_blake2s_init(&ctx, WIREGUARD_HASH_LEN, NULL, 0); // init context for 2nd pass
wireguard_blake2s_update(&ctx, k_opad, WIREGUARD_BLAKE2S_BLOCK_SIZE); // start with outer pad
wireguard_blake2s_update(&ctx, digest, WIREGUARD_HASH_LEN); // then results of 1st hash
wireguard_blake2s_final(&ctx, digest); // finish up 2nd pass
}
static void wireguard_kdf1(uint8_t *tau1, const uint8_t *chaining_key, const uint8_t *data, size_t data_len) {
uint8_t tau0[WIREGUARD_HASH_LEN];
uint8_t output[WIREGUARD_HASH_LEN + 1];
// tau0 = Hmac(key, input)
wireguard_hmac(tau0, chaining_key, WIREGUARD_HASH_LEN, data, data_len);
// tau1 := Hmac(tau0, 0x1)
output[0] = 1;
wireguard_hmac(output, tau0, WIREGUARD_HASH_LEN, output, 1);
memcpy(tau1, output, WIREGUARD_HASH_LEN);
// Wipe intermediates
crypto_zero(tau0, sizeof(tau0));
crypto_zero(output, sizeof(output));
}
static void wireguard_kdf2(uint8_t *tau1, uint8_t *tau2, const uint8_t *chaining_key, const uint8_t *data, size_t data_len) {
uint8_t tau0[WIREGUARD_HASH_LEN];
uint8_t output[WIREGUARD_HASH_LEN + 1];
// tau0 = Hmac(key, input)
wireguard_hmac(tau0, chaining_key, WIREGUARD_HASH_LEN, data, data_len);
// tau1 := Hmac(tau0, 0x1)
output[0] = 1;
wireguard_hmac(output, tau0, WIREGUARD_HASH_LEN, output, 1);
memcpy(tau1, output, WIREGUARD_HASH_LEN);
// tau2 := Hmac(tau0,tau1 || 0x2)
output[WIREGUARD_HASH_LEN] = 2;
wireguard_hmac(output, tau0, WIREGUARD_HASH_LEN, output, WIREGUARD_HASH_LEN + 1);
memcpy(tau2, output, WIREGUARD_HASH_LEN);
// Wipe intermediates
crypto_zero(tau0, sizeof(tau0));
crypto_zero(output, sizeof(output));
}
static void wireguard_kdf3(uint8_t *tau1, uint8_t *tau2, uint8_t *tau3, const uint8_t *chaining_key, const uint8_t *data, size_t data_len) {
uint8_t tau0[WIREGUARD_HASH_LEN];
uint8_t output[WIREGUARD_HASH_LEN + 1];
// tau0 = Hmac(key, input)
wireguard_hmac(tau0, chaining_key, WIREGUARD_HASH_LEN, data, data_len);
// tau1 := Hmac(tau0, 0x1)
output[0] = 1;
wireguard_hmac(output, tau0, WIREGUARD_HASH_LEN, output, 1);
memcpy(tau1, output, WIREGUARD_HASH_LEN);
// tau2 := Hmac(tau0,tau1 || 0x2)
output[WIREGUARD_HASH_LEN] = 2;
wireguard_hmac(output, tau0, WIREGUARD_HASH_LEN, output, WIREGUARD_HASH_LEN + 1);
memcpy(tau2, output, WIREGUARD_HASH_LEN);
// tau3 := Hmac(tau0,tau1,tau2 || 0x3)
output[WIREGUARD_HASH_LEN] = 3;
wireguard_hmac(output, tau0, WIREGUARD_HASH_LEN, output, WIREGUARD_HASH_LEN + 1);
memcpy(tau3, output, WIREGUARD_HASH_LEN);
// Wipe intermediates
crypto_zero(tau0, sizeof(tau0));
crypto_zero(output, sizeof(output));
}
bool wireguard_check_replay(wireguard_keypair_t *keypair, uint64_t seq) {
// AddLog(LOG_LEVEL_INFO, ">>>: wireguard_check_replay");
// Implementation of packet replay window - as per RFC2401
// Adapted from code in Appendix C at https://tools.ietf.org/html/rfc2401
uint32_t diff;
bool result = false;
size_t ReplayWindowSize = sizeof(keypair->replay_bitmap) * CHAR_BIT; // 32 bits
// WireGuard data packet counter starts from 0 but algorithm expects packet numbers to start from 1
seq++;
if (seq != 0) {
if (seq > keypair->replay_counter) {
// new larger sequence number
diff = seq - keypair->replay_counter;
if (diff < ReplayWindowSize) {
// In window
keypair->replay_bitmap <<= diff;
// set bit for this packet
keypair->replay_bitmap |= 1;
} else {
// This packet has a "way larger"
keypair->replay_bitmap = 1;
}
keypair->replay_counter = seq;
// larger is good
result = true;
} else {
diff = keypair->replay_counter - seq;
if (diff < ReplayWindowSize) {
if (keypair->replay_bitmap & ((uint32_t)1 << diff)) {
// already seen
} else {
// mark as seen
keypair->replay_bitmap |= ((uint32_t)1 << diff);
// out of order but good
result = true;
}
} else {
// too old or wrapped
}
}
} else {
// first == 0 or wrapped
}
return result;
}
wireguard_keypair_t *get_peer_keypair_for_idx(wireguard_peer_t *peer, uint32_t idx) {
if (peer->curr_keypair.valid && peer->curr_keypair.local_index == idx) {
return &peer->curr_keypair;
} else if (peer->next_keypair.valid && peer->next_keypair.local_index == idx) {
return &peer->next_keypair;
} else if (peer->prev_keypair.valid && peer->prev_keypair.local_index == idx) {
return &peer->prev_keypair;
}
return NULL;
}
static uint32_t wireguard_generate_unique_index(struct wireguard_device *device) {
// We need a random 32-bit number but make sure it's not already been used in the context of this device
uint32_t result;
uint8_t buf[4];
wireguard_peer_t *peer;
bool existing = false;
do {
do {
wireguard_random_bytes(buf, 4);
result = U8TO32_LITTLE(buf);
} while ((result == 0) || (result == 0xFFFFFFFF)); // Don't allow 0 or 0xFFFFFFFF as valid values
existing = false;
for (int32_t x=0; x < WIREGUARD_MAX_PEERS; x++) {
peer = &device->peers[x];
existing = (result == peer->curr_keypair.local_index) ||
(result == peer->prev_keypair.local_index) ||
(result == peer->next_keypair.local_index) ||
(result == peer->handshake.local_index);
}
} while (existing);
return result;
}
static void wireguard_clamp_private_key(uint8_t *key) {
key[0] &= 248;
key[31] = (key[31] & 127) | 64;
}
static void wireguard_generate_private_key(uint8_t *key) {
wireguard_random_bytes(key, WIREGUARD_PRIVATE_KEY_LEN);
wireguard_clamp_private_key(key);
}
static bool wireguard_generate_public_key(uint8_t *public_key, const uint8_t *private_key) {
static const uint8_t basepoint[WIREGUARD_PUBLIC_KEY_LEN] = { 9 };
bool result = false;
if (memcmp(private_key, zero_key, WIREGUARD_PUBLIC_KEY_LEN) != 0) {
result = (wireguard_x25519(public_key, private_key, basepoint) == 0);
}
return result;
}
bool wireguard_check_mac1(struct wireguard_device *device, const uint8_t *data, size_t len, const uint8_t *mac1) {
bool result = false;
uint8_t calculated[WIREGUARD_COOKIE_LEN];
wireguard_mac(calculated, data, len, device->label_mac1_key, WIREGUARD_SESSION_KEY_LEN);
if (crypto_equal(calculated, mac1, WIREGUARD_COOKIE_LEN)) {
result = true;
}
return result;
}
bool wireguard_check_mac2(struct wireguard_device *device, const uint8_t *data, size_t len, uint8_t *source_addr_port, size_t source_length, const uint8_t *mac2) {
// AddLog(LOG_LEVEL_INFO, ">>>: wireguard_check_mac2");
bool result = false;
uint8_t cookie[WIREGUARD_COOKIE_LEN];
uint8_t calculated[WIREGUARD_COOKIE_LEN];
generate_peer_cookie(device, cookie, source_addr_port, source_length);
wireguard_mac(calculated, data, len, cookie, WIREGUARD_COOKIE_LEN);
if (crypto_equal(calculated, mac2, WIREGUARD_COOKIE_LEN)) {
result = true;
}
return result;
}
void keypair_destroy(wireguard_keypair_t *keypair) {
crypto_zero(keypair, sizeof(wireguard_keypair_t));
keypair->valid = false;
}
void keypair_update(wireguard_peer_t *peer, wireguard_keypair_t *received_keypair) {
bool key_is_next = (received_keypair == &peer->next_keypair);
if (key_is_next) {
peer->prev_keypair = peer->curr_keypair;
peer->curr_keypair = peer->next_keypair;
keypair_destroy(&peer->next_keypair);
}
}
static void add_new_keypair(wireguard_peer_t *peer, wireguard_keypair_t new_keypair) {
if (new_keypair.initiator) {
if (peer->next_keypair.valid) {
peer->prev_keypair = peer->next_keypair;
keypair_destroy(&peer->next_keypair);
} else {
peer->prev_keypair = peer->curr_keypair;
}
peer->curr_keypair = new_keypair;
} else {
peer->next_keypair = new_keypair;
keypair_destroy(&peer->prev_keypair);
}
peer->latest_handshake_millis = new_keypair.keypair_millis;
}
void wireguard_start_session(wireguard_peer_t *peer, bool initiator) {
wireguard_handshake_t *handshake = &peer->handshake;
wireguard_keypair_t new_keypair;
crypto_zero(&new_keypair, sizeof(wireguard_keypair_t));
new_keypair.initiator = initiator;
new_keypair.local_index = handshake->local_index;
new_keypair.remote_index = handshake->remote_index;
new_keypair.keypair_millis = wireguard_sys_now();
new_keypair.sending_valid = true;
new_keypair.receiving_valid = true;
// 5.4.5 Transport Data Key Derivation
// (Tsendi = Trecvr, Trecvi = Tsendr) := Kdf2(Ci = Cr,E)
if (new_keypair.initiator) {
wireguard_kdf2(new_keypair.sending_key, new_keypair.receiving_key, handshake->chaining_key, NULL, 0);
} else {
wireguard_kdf2(new_keypair.receiving_key, new_keypair.sending_key, handshake->chaining_key, NULL, 0);
}
new_keypair.replay_bitmap = 0;
new_keypair.replay_counter = 0;
new_keypair.last_tx = 0;
new_keypair.last_rx = 0; // No packets received yet
new_keypair.valid = true;
// Eprivi = Epubi = Eprivr = Epubr = Ci = Cr := E
crypto_zero(handshake->ephemeral_private, WIREGUARD_PUBLIC_KEY_LEN);
crypto_zero(handshake->remote_ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
crypto_zero(handshake->hash, WIREGUARD_HASH_LEN);
crypto_zero(handshake->chaining_key, WIREGUARD_HASH_LEN);
handshake->remote_index = 0;
handshake->local_index = 0;
handshake->valid = false;
add_new_keypair(peer, new_keypair);
}
uint8_t wireguard_get_message_type(const uint8_t *data, size_t len) {
uint8_t result = MESSAGE_INVALID;
if (len >= 4) {
if ((data[1] == 0) && (data[2] == 0) && (data[3] == 0)) {
switch (data[0]) {
case MESSAGE_HANDSHAKE_INITIATION:
if (len == sizeof(struct message_handshake_initiation)) {
result = MESSAGE_HANDSHAKE_INITIATION;
}
break;
case MESSAGE_HANDSHAKE_RESPONSE:
if (len == sizeof(struct message_handshake_response)) {
result = MESSAGE_HANDSHAKE_RESPONSE;
}
break;
case MESSAGE_COOKIE_REPLY:
if (len == sizeof(struct message_cookie_reply)) {
result = MESSAGE_COOKIE_REPLY;
}
break;
case MESSAGE_TRANSPORT_DATA:
if (len >= sizeof(struct message_transport_data) + WIREGUARD_AUTHTAG_LEN) {
result = MESSAGE_TRANSPORT_DATA;
}
break;
default:
break;
}
}
}
return result;
}
wireguard_peer_t *wireguard_process_initiation_message(struct wireguard_device *device, struct message_handshake_initiation *msg) {
// AddLog(LOG_LEVEL_DEBUG, ">>>: wireguard_process_initiation_message");
wireguard_peer_t *ret_peer = NULL;
wireguard_peer_t *peer = NULL;
wireguard_handshake_t *handshake;
uint8_t key[WIREGUARD_SESSION_KEY_LEN];
uint8_t chaining_key[WIREGUARD_HASH_LEN];
uint8_t hash[WIREGUARD_HASH_LEN];
uint8_t s[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t e[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t t[WIREGUARD_TAI64N_LEN];
uint8_t dh_calculation[WIREGUARD_PUBLIC_KEY_LEN];
uint32_t now;
bool rate_limit;
bool replay;
// We are the responder, other end is the initiator
// Ci := Hash(Construction) (precalculated hash)
memcpy(chaining_key, construction_hash, WIREGUARD_HASH_LEN);
// Hi := Hash(Ci || Identifier
memcpy(hash, identifier_hash, WIREGUARD_HASH_LEN);
// Hi := Hash(Hi || Spubr)
wireguard_mix_hash(hash, device->public_key, WIREGUARD_PUBLIC_KEY_LEN);
// Ci := Kdf1(Ci, Epubi)
wireguard_kdf1(chaining_key, chaining_key, msg->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// msg.ephemeral := Epubi
memcpy(e, msg->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// Hi := Hash(Hi || msg.ephemeral)
wireguard_mix_hash(hash, msg->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// Calculate DH(Eprivi,Spubr)
wireguard_x25519(dh_calculation, device->private_key, e);
if (!crypto_equal(dh_calculation, zero_key, WIREGUARD_PUBLIC_KEY_LEN)) {
// (Ci,k) := Kdf2(Ci,DH(Eprivi,Spubr))
wireguard_kdf2(chaining_key, key, chaining_key, dh_calculation, WIREGUARD_PUBLIC_KEY_LEN);
// msg.static := AEAD(k, 0, Spubi, Hi)
if (wireguard_aead_decrypt(s, msg->enc_static, sizeof(msg->enc_static), hash, WIREGUARD_HASH_LEN, 0, key)) {
// Hi := Hash(Hi || msg.static)
wireguard_mix_hash(hash, msg->enc_static, sizeof(msg->enc_static));
peer = wireguard_peer_lookup_by_pubkey(device, s);
if (peer) {
handshake = &peer->handshake;
// (Ci,k) := Kdf2(Ci,DH(Sprivi,Spubr))
wireguard_kdf2(chaining_key, key, chaining_key, peer->public_key_dh, WIREGUARD_PUBLIC_KEY_LEN);
// msg.timestamp := AEAD(k, 0, Timestamp(), Hi)
if (wireguard_aead_decrypt(t, msg->enc_timestamp, sizeof(msg->enc_timestamp), hash, WIREGUARD_HASH_LEN, 0, key)) {
// Hi := Hash(Hi || msg.timestamp)
wireguard_mix_hash(hash, msg->enc_timestamp, sizeof(msg->enc_timestamp));
now = wireguard_sys_now();
// Check that timestamp is increasing and we haven't had too many initiations (should only get one per peer every 5 seconds max?)
replay = (memcmp(t, peer->greatest_timestamp, WIREGUARD_TAI64N_LEN) <= 0); // tai64n is big endian so we can use memcmp to compare
rate_limit = (peer->last_initiation_rx - now) < (1000 / MAX_INITIATIONS_PER_SECOND);
if (!replay && !rate_limit) {
// Success! Copy everything to peer
peer->last_initiation_rx = now;
if (memcmp(t, peer->greatest_timestamp, WIREGUARD_TAI64N_LEN) > 0) {
memcpy(peer->greatest_timestamp, t, WIREGUARD_TAI64N_LEN);
// TODO: Need to notify if the higher layers want to persist latest timestamp/nonce somewhere
}
memcpy(handshake->remote_ephemeral, e, WIREGUARD_PUBLIC_KEY_LEN);
memcpy(handshake->hash, hash, WIREGUARD_HASH_LEN);
memcpy(handshake->chaining_key, chaining_key, WIREGUARD_HASH_LEN);
handshake->remote_index = msg->sender;
handshake->valid = true;
handshake->initiator = false;
ret_peer = peer;
} else {
// Ignore
}
} else {
AddLog(LOG_LEVEL_INFO, PSTR("WG : wireguard_process_initiation_message: failed to decrypt"));
}
} else {
AddLog(LOG_LEVEL_INFO, PSTR("WG : peer not found"));
}
} else {
AddLog(LOG_LEVEL_INFO, PSTR("WG : Failed to decrypt"));
}
} else {
AddLog(LOG_LEVEL_INFO, PSTR("WG : Bad X25519"));
}
crypto_zero(key, sizeof(key));
crypto_zero(hash, sizeof(hash));
crypto_zero(chaining_key, sizeof(chaining_key));
crypto_zero(dh_calculation, sizeof(dh_calculation));
return ret_peer;
}
bool wireguard_process_handshake_response(struct wireguard_device *device, wireguard_peer_t *peer, struct message_handshake_response *src) {
wireguard_handshake_t *handshake = &peer->handshake;
bool result = false;
uint8_t key[WIREGUARD_SESSION_KEY_LEN];
uint8_t hash[WIREGUARD_HASH_LEN];
uint8_t chaining_key[WIREGUARD_HASH_LEN];
uint8_t e[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t ephemeral_private[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t static_private[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t preshared_key[WIREGUARD_SESSION_KEY_LEN];
uint8_t dh_calculation[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t tau[WIREGUARD_PUBLIC_KEY_LEN];
if (handshake->valid && handshake->initiator) {
memcpy(hash, handshake->hash, WIREGUARD_HASH_LEN);
memcpy(chaining_key, handshake->chaining_key, WIREGUARD_HASH_LEN);
memcpy(ephemeral_private, handshake->ephemeral_private, WIREGUARD_PUBLIC_KEY_LEN);
memcpy(preshared_key, peer->preshared_key, WIREGUARD_SESSION_KEY_LEN);
// (Eprivr, Epubr) := DH-Generate()
// Not required
// Cr := Kdf1(Cr,Epubr)
wireguard_kdf1(chaining_key, chaining_key, src->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// msg.ephemeral := Epubr
memcpy(e, src->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// Hr := Hash(Hr || msg.ephemeral)
wireguard_mix_hash(hash, src->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// Cr := Kdf1(Cr, DH(Eprivr, Epubi))
// Calculate DH(Eprivr, Epubi)
wireguard_x25519(dh_calculation, ephemeral_private, e);
if (!crypto_equal(dh_calculation, zero_key, WIREGUARD_PUBLIC_KEY_LEN)) {
wireguard_kdf1(chaining_key, chaining_key, dh_calculation, WIREGUARD_PUBLIC_KEY_LEN);
// Cr := Kdf1(Cr, DH(Eprivr, Spubi))
// CalculateDH(Eprivr, Spubi)
wireguard_x25519(dh_calculation, device->private_key, e);
if (!crypto_equal(dh_calculation, zero_key, WIREGUARD_PUBLIC_KEY_LEN)) {
wireguard_kdf1(chaining_key, chaining_key, dh_calculation, WIREGUARD_PUBLIC_KEY_LEN);
// (Cr, t, k) := Kdf3(Cr, Q)
wireguard_kdf3(chaining_key, tau, key, chaining_key, peer->preshared_key, WIREGUARD_SESSION_KEY_LEN);
// Hr := Hash(Hr | t)
wireguard_mix_hash(hash, tau, WIREGUARD_HASH_LEN);
// msg.empty := AEAD(k, 0, E, Hr)
if (wireguard_aead_decrypt(NULL, src->enc_empty, sizeof(src->enc_empty), hash, WIREGUARD_HASH_LEN, 0, key)) {
// Hr := Hash(Hr | msg.empty)
// Not required as discarded
//Copy details to handshake
memcpy(handshake->remote_ephemeral, e, WIREGUARD_HASH_LEN);
memcpy(handshake->hash, hash, WIREGUARD_HASH_LEN);
memcpy(handshake->chaining_key, chaining_key, WIREGUARD_HASH_LEN);
handshake->remote_index = src->sender;
result = true;
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : Decrypt failed"));
}
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : X25519 fail"));
}
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : X25519 fail 2"));
}
}
crypto_zero(key, sizeof(key));
crypto_zero(hash, sizeof(hash));
crypto_zero(chaining_key, sizeof(chaining_key));
crypto_zero(ephemeral_private, sizeof(ephemeral_private));
crypto_zero(static_private, sizeof(static_private));
crypto_zero(preshared_key, sizeof(preshared_key));
crypto_zero(tau, sizeof(tau));
// AddLog(LOG_LEVEL_DEBUG, ">>>: OUT wireguard_process_handshake_response result %d (1==SUCCESS)");
return result;
}
bool wireguard_process_cookie_message(struct wireguard_device *device, wireguard_peer_t *peer, struct message_cookie_reply *src) {
// AddLog(LOG_LEVEL_INFO, ">>>: wireguard_process_cookie_message");
uint8_t cookie[WIREGUARD_COOKIE_LEN];
bool result = false;
if (peer->handshake_mac1_valid) {
result = wireguard_xaead_decrypt(cookie, src->enc_cookie, sizeof(src->enc_cookie), peer->handshake_mac1, WIREGUARD_COOKIE_LEN, src->nonce, peer->label_cookie_key);
if (result) {
// 5.4.7 Under Load: Cookie Reply Message
// Upon receiving this message, if it is valid, the only thing the recipient of this message should do is store the cookie along with the time at which it was received
memcpy(peer->cookie, cookie, WIREGUARD_COOKIE_LEN);
peer->cookie_millis = wireguard_sys_now();
peer->handshake_mac1_valid = false;
}
} else {
// We didn't send any initiation packet so we shouldn't be getting a cookie reply!
}
return result;
}
bool wireguard_create_handshake_initiation(struct wireguard_device *device, wireguard_peer_t *peer, struct message_handshake_initiation *dst) {
uint8_t timestamp[WIREGUARD_TAI64N_LEN];
uint8_t key[WIREGUARD_SESSION_KEY_LEN];
uint8_t dh_calculation[WIREGUARD_PUBLIC_KEY_LEN];
bool result = false;
wireguard_handshake_t *handshake = &peer->handshake;
memset(dst, 0, sizeof(struct message_handshake_initiation));
// Ci := Hash(Construction) (precalculated hash)
memcpy(handshake->chaining_key, construction_hash, WIREGUARD_HASH_LEN);
// Hi := Hash(Ci || Identifier)
memcpy(handshake->hash, identifier_hash, WIREGUARD_HASH_LEN);
// Hi := Hash(Hi || Spubr)
wireguard_mix_hash(handshake->hash, peer->public_key, WIREGUARD_PUBLIC_KEY_LEN);
// (Eprivi, Epubi) := DH-Generate()
wireguard_generate_private_key(handshake->ephemeral_private);
if (wireguard_generate_public_key(dst->ephemeral, handshake->ephemeral_private)) {
// Ci := Kdf1(Ci, Epubi)
wireguard_kdf1(handshake->chaining_key, handshake->chaining_key, dst->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// msg.ephemeral := Epubi
// Done above - public keys is calculated into dst->ephemeral
// Hi := Hash(Hi || msg.ephemeral)
wireguard_mix_hash(handshake->hash, dst->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// Calculate DH(Eprivi,Spubr)
wireguard_x25519(dh_calculation, handshake->ephemeral_private, peer->public_key);
if (!crypto_equal(dh_calculation, zero_key, WIREGUARD_PUBLIC_KEY_LEN)) {
// (Ci,k) := Kdf2(Ci,DH(Eprivi,Spubr))
wireguard_kdf2(handshake->chaining_key, key, handshake->chaining_key, dh_calculation, WIREGUARD_PUBLIC_KEY_LEN);
// msg.static := AEAD(k,0,Spubi, Hi)
wireguard_aead_encrypt(dst->enc_static, device->public_key, WIREGUARD_PUBLIC_KEY_LEN, handshake->hash, WIREGUARD_HASH_LEN, 0, key);
// Hi := Hash(Hi || msg.static)
wireguard_mix_hash(handshake->hash, dst->enc_static, sizeof(dst->enc_static));
// (Ci,k) := Kdf2(Ci,DH(Sprivi,Spubr))
// note DH(Sprivi,Spubr) is precomputed per peer
wireguard_kdf2(handshake->chaining_key, key, handshake->chaining_key, peer->public_key_dh, WIREGUARD_PUBLIC_KEY_LEN);
// msg.timestamp := AEAD(k, 0, Timestamp(), Hi)
wireguard_tai64n_now(timestamp);
wireguard_aead_encrypt(dst->enc_timestamp, timestamp, WIREGUARD_TAI64N_LEN, handshake->hash, WIREGUARD_HASH_LEN, 0, key);
// Hi := Hash(Hi || msg.timestamp)
wireguard_mix_hash(handshake->hash, dst->enc_timestamp, sizeof(dst->enc_timestamp));
dst->type = MESSAGE_HANDSHAKE_INITIATION;
dst->sender = wireguard_generate_unique_index(device);
handshake->valid = true;
handshake->initiator = true;
handshake->local_index = dst->sender;
result = true;
}
}
if (result) {
// 5.4.4 Cookie MACs
// msg.mac1 := Mac(Hash(Label-Mac1 || Spubm' ), msgA)
// The value Hash(Label-Mac1 || Spubm' ) above can be pre-computed
wireguard_mac(dst->mac1, dst, (sizeof(struct message_handshake_initiation)-(2*WIREGUARD_COOKIE_LEN)), peer->label_mac1_key, WIREGUARD_SESSION_KEY_LEN);
// if Lm = E or Lm ≥ 120:
if ((peer->cookie_millis == 0) || wireguard_expired(peer->cookie_millis, COOKIE_SECRET_MAX_AGE)) {
// msg.mac2 := 0
crypto_zero(dst->mac2, WIREGUARD_COOKIE_LEN);
} else {
// msg.mac2 := Mac(Lm, msgB)
wireguard_mac(dst->mac2, dst, (sizeof(struct message_handshake_initiation)-(WIREGUARD_COOKIE_LEN)), peer->cookie, WIREGUARD_COOKIE_LEN);
}
}
crypto_zero(key, sizeof(key));
crypto_zero(dh_calculation, sizeof(dh_calculation));
return result;
}
bool wireguard_create_handshake_response(struct wireguard_device *device, wireguard_peer_t *peer, struct message_handshake_response *dst) {
wireguard_handshake_t *handshake = &peer->handshake;
uint8_t key[WIREGUARD_SESSION_KEY_LEN];
uint8_t dh_calculation[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t tau[WIREGUARD_HASH_LEN];
bool result = false;
memset(dst, 0, sizeof(struct message_handshake_response));
if (handshake->valid && !handshake->initiator) {
// (Eprivr, Epubr) := DH-Generate()
wireguard_generate_private_key(handshake->ephemeral_private);
if (wireguard_generate_public_key(dst->ephemeral, handshake->ephemeral_private)) {
// Cr := Kdf1(Cr,Epubr)
wireguard_kdf1(handshake->chaining_key, handshake->chaining_key, dst->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// msg.ephemeral := Epubr
// Copied above when generated
// Hr := Hash(Hr || msg.ephemeral)
wireguard_mix_hash(handshake->hash, dst->ephemeral, WIREGUARD_PUBLIC_KEY_LEN);
// Cr := Kdf1(Cr, DH(Eprivr, Epubi))
// Calculate DH(Eprivi,Spubr)
wireguard_x25519(dh_calculation, handshake->ephemeral_private, handshake->remote_ephemeral);
if (!crypto_equal(dh_calculation, zero_key, WIREGUARD_PUBLIC_KEY_LEN)) {
wireguard_kdf1(handshake->chaining_key, handshake->chaining_key, dh_calculation, WIREGUARD_PUBLIC_KEY_LEN);
// Cr := Kdf1(Cr, DH(Eprivr, Spubi))
// Calculate DH(Eprivi,Spubr)
wireguard_x25519(dh_calculation, handshake->ephemeral_private, peer->public_key);
if (!crypto_equal(dh_calculation, zero_key, WIREGUARD_PUBLIC_KEY_LEN)) {
wireguard_kdf1(handshake->chaining_key, handshake->chaining_key, dh_calculation, WIREGUARD_PUBLIC_KEY_LEN);
// (Cr, t, k) := Kdf3(Cr, Q)
wireguard_kdf3(handshake->chaining_key, tau, key, handshake->chaining_key, peer->preshared_key, WIREGUARD_SESSION_KEY_LEN);
// Hr := Hash(Hr | t)
wireguard_mix_hash(handshake->hash, tau, WIREGUARD_HASH_LEN);
// msg.empty := AEAD(k, 0, E, Hr)
wireguard_aead_encrypt(dst->enc_empty, NULL, 0, handshake->hash, WIREGUARD_HASH_LEN, 0, key);
// Hr := Hash(Hr | msg.empty)
wireguard_mix_hash(handshake->hash, dst->enc_empty, sizeof(dst->enc_empty));
dst->type = MESSAGE_HANDSHAKE_RESPONSE;
dst->receiver = handshake->remote_index;
dst->sender = wireguard_generate_unique_index(device);
// Update handshake object too
handshake->local_index = dst->sender;
result = true;
} else {
// Bad x25519
}
} else {
// Bad x25519
}
} else {
// Failed to generate DH
}
}
if (result) {
// 5.4.4 Cookie MACs
// msg.mac1 := Mac(Hash(Label-Mac1 || Spubm' ), msgA)
// The value Hash(Label-Mac1 || Spubm' ) above can be pre-computed
wireguard_mac(dst->mac1, dst, (sizeof(struct message_handshake_response)-(2*WIREGUARD_COOKIE_LEN)), peer->label_mac1_key, WIREGUARD_SESSION_KEY_LEN);
// if Lm = E or Lm ≥ 120:
if ((peer->cookie_millis == 0) || wireguard_expired(peer->cookie_millis, COOKIE_SECRET_MAX_AGE)) {
// msg.mac2 := 0
crypto_zero(dst->mac2, WIREGUARD_COOKIE_LEN);
} else {
// msg.mac2 := Mac(Lm, msgB)
wireguard_mac(dst->mac2, dst, (sizeof(struct message_handshake_response)-(WIREGUARD_COOKIE_LEN)), peer->cookie, WIREGUARD_COOKIE_LEN);
}
}
crypto_zero(key, sizeof(key));
crypto_zero(dh_calculation, sizeof(dh_calculation));
crypto_zero(tau, sizeof(tau));
return result;
}
void wireguard_create_cookie_reply(struct wireguard_device *device, struct message_cookie_reply *dst, const uint8_t *mac1, uint32_t index, uint8_t *source_addr_port, size_t source_length) {
uint8_t cookie[WIREGUARD_COOKIE_LEN];
crypto_zero(dst, sizeof(struct message_cookie_reply));
dst->type = MESSAGE_COOKIE_REPLY;
dst->receiver = index;
wireguard_random_bytes(dst->nonce, COOKIE_NONCE_LEN);
generate_peer_cookie(device, cookie, source_addr_port, source_length);
wireguard_xaead_encrypt(dst->enc_cookie, cookie, WIREGUARD_COOKIE_LEN, mac1, WIREGUARD_COOKIE_LEN, dst->nonce, device->label_cookie_key);
}
bool wireguard_peer_init(struct wireguard_device *device, wireguard_peer_t *peer, const uint8_t *public_key, const uint8_t *preshared_key) {
// Clear out structure
memset(peer, 0, sizeof(wireguard_peer_t));
if (device->valid) {
// Copy across the public key into our peer structure
memcpy(peer->public_key, public_key, WIREGUARD_PUBLIC_KEY_LEN);
memcpy(peer->preshared_key, preshared_key, WIREGUARD_SESSION_KEY_LEN);
// AddLog(LOG_LEVEL_INFO, ">>>: wireguard_peer_init pub_key %*_H priv_key %*_H peer_pub %*_H", 32, peer->public_key_dh, 32, device->private_key, 32, peer->public_key);
if (wireguard_x25519(peer->public_key_dh, device->private_key, peer->public_key) == 0) {
// Zero out handshake
memset(&peer->handshake, 0, sizeof(wireguard_handshake_t));
peer->handshake.valid = false;
// Zero out any cookie info - we haven't received one yet
peer->cookie_millis = 0;
memset(&peer->cookie, 0, WIREGUARD_COOKIE_LEN);
// Precompute keys to deal with mac1/2 calculation
wireguard_mac_key(peer->label_mac1_key, peer->public_key, LABEL_MAC1, sizeof(LABEL_MAC1) - 1);
wireguard_mac_key(peer->label_cookie_key, peer->public_key, LABEL_COOKIE, sizeof(LABEL_COOKIE) - 1);
peer->valid = true;
} else {
crypto_zero(peer->public_key_dh, WIREGUARD_PUBLIC_KEY_LEN);
}
}
return peer->valid;
}
bool wireguard_device_init(struct wireguard_device *device, const uint8_t *private_key) {
// Set the private key and calculate public key from it
memcpy(device->private_key, private_key, WIREGUARD_PRIVATE_KEY_LEN);
// Ensure private key is correctly "clamped"
wireguard_clamp_private_key(device->private_key);
device->valid = wireguard_generate_public_key(device->public_key, private_key);
if (device->valid) {
// AddLog(LOG_LEVEL_DEBUG, ">>>: wireguard_device_init public_key %*_H", 32, device->public_key);
generate_cookie_secret(device);
// AddLog(LOG_LEVEL_DEBUG, ">>>: wireguard_device_init cookie secret %*_H", 32, device->cookie_secret);
// 5.4.4 Cookie MACs - The value Hash(Label-Mac1 || Spubm' ) above can be pre-computed.
wireguard_mac_key(device->label_mac1_key, device->public_key, LABEL_MAC1, sizeof(LABEL_MAC1) - 1);
// AddLog(LOG_LEVEL_DEBUG, ">>>: wireguard_device_init label_mac1_key %*_H", 32, device->label_mac1_key);
// 5.4.7 Under Load: Cookie Reply Message - The value Hash(Label-Cookie || Spubm) above can be pre-computed.
wireguard_mac_key(device->label_cookie_key, device->public_key, LABEL_COOKIE, sizeof(LABEL_COOKIE) - 1);
// AddLog(LOG_LEVEL_DEBUG, ">>>: wireguard_device_init label_cookie_key %*_H", 32, device->label_cookie_key);
} else {
crypto_zero(device->private_key, WIREGUARD_PRIVATE_KEY_LEN);
}
return device->valid;
}
void wireguard_encrypt_packet(uint8_t *dst, const uint8_t *src, size_t src_len, wireguard_keypair_t *keypair) {
wireguard_aead_encrypt(dst, src, src_len, NULL, 0, keypair->sending_counter, keypair->sending_key);
keypair->sending_counter++;
}
bool wireguard_decrypt_packet(uint8_t *dst, const uint8_t *src, size_t src_len, uint64_t counter, wireguard_keypair_t *keypair) {
return wireguard_aead_decrypt(dst, src, src_len, NULL, 0, counter, keypair->receiving_key);
}

View File

@ -0,0 +1,288 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* Copyright (c) 2025 Stephan Hadinger
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
#ifndef _WIREGUARD_H_
#define _WIREGUARD_H_
#ifdef __cplusplus
extern "C" {
#endif
// Note: these are only required for definitions in device/peer for netif, udp_pcb, ip_addr_t and u16_t
#include "lwip/netif.h"
#include "lwip/ip_addr.h"
#include "lwip/arch.h"
// Platform-specific functions that need to be implemented per-platform
#include "wireguard-platform.h"
// tai64n contains 64-bit seconds and 32-bit nano offset (12 bytes)
#define WIREGUARD_TAI64N_LEN (12)
// Auth algorithm is chacha20pol1305 which is 128bit (16 byte) authenticator
#define WIREGUARD_AUTHTAG_LEN (16)
// Hash algorithm is blake2s which makes 32 byte hashes
#define WIREGUARD_HASH_LEN (32)
// Public key algo is curve22519 which uses 32 byte keys
#define WIREGUARD_PUBLIC_KEY_LEN (32)
// Public key algo is curve22519 which uses 32 byte keys
#define WIREGUARD_PRIVATE_KEY_LEN (32)
// Symmetric session keys are chacha20/poly1305 which uses 32 byte keys
#define WIREGUARD_SESSION_KEY_LEN (32)
// Timers / Limits
#define WIREGUARD_COOKIE_LEN (16)
#define COOKIE_SECRET_MAX_AGE (2 * 60)
#define COOKIE_NONCE_LEN (24)
#define REKEY_AFTER_MESSAGES (1ULL << 60)
#define REJECT_AFTER_MESSAGES (0xFFFFFFFFFFFFFFFFULL - (1ULL << 13))
#define REKEY_AFTER_TIME (120)
#define REJECT_AFTER_TIME (180)
#define REKEY_TIMEOUT (5)
typedef struct wireguard_keypair {
bool valid;
bool initiator; // Did we initiate this session (send the initiation packet rather than sending the response packet)
uint32_t keypair_millis;
uint8_t sending_key[WIREGUARD_SESSION_KEY_LEN];
bool sending_valid;
uint64_t sending_counter;
uint8_t receiving_key[WIREGUARD_SESSION_KEY_LEN];
bool receiving_valid;
uint32_t last_tx;
uint32_t last_rx;
uint32_t replay_bitmap;
uint64_t replay_counter;
uint32_t local_index; // This is the index we generated for our end
uint32_t remote_index; // This is the index on the other end
} wireguard_keypair_t;
typedef struct wireguard_handshake {
bool valid;
bool initiator;
uint32_t local_index;
uint32_t remote_index;
uint8_t ephemeral_private[WIREGUARD_PRIVATE_KEY_LEN];
uint8_t remote_ephemeral[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t hash[WIREGUARD_HASH_LEN];
uint8_t chaining_key[WIREGUARD_HASH_LEN];
} wireguard_handshake_t;
typedef struct wireguard_allowed_ip {
bool valid;
ip_addr_t ip;
ip_addr_t mask;
} wireguard_allowed_ip_t;
typedef struct wireguard_peer {
bool valid; // Is this peer initialised?
bool active; // Should we be actively trying to connect?
// This is the configured IP of the peer (endpoint)
ip_addr_t connect_ip;
u16_t connect_port;
// This is the latest received IP/port
ip_addr_t ip;
u16_t port;
// keep-alive interval in seconds, 0 is disable
uint16_t keepalive_interval;
struct wireguard_allowed_ip allowed_source_ips[WIREGUARD_MAX_SRC_IPS];
uint8_t public_key[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t preshared_key[WIREGUARD_SESSION_KEY_LEN];
// Precomputed DH(Sprivi,Spubr) with device private key, and peer public key
uint8_t public_key_dh[WIREGUARD_PUBLIC_KEY_LEN];
// Session keypairs
struct wireguard_keypair curr_keypair;
struct wireguard_keypair prev_keypair;
struct wireguard_keypair next_keypair;
// 5.1 Silence is a Virtue: The responder keeps track of the greatest timestamp received per peer
uint8_t greatest_timestamp[WIREGUARD_TAI64N_LEN];
// The active handshake that is happening
struct wireguard_handshake handshake;
// The time of the latest completed handshake
uint32_t latest_handshake_millis;
// Decrypted cookie from the responder
uint32_t cookie_millis;
uint8_t cookie[WIREGUARD_COOKIE_LEN];
// The latest mac1 we sent with initiation
bool handshake_mac1_valid;
uint8_t handshake_mac1[WIREGUARD_COOKIE_LEN];
// Precomputed keys for use in mac validation
uint8_t label_cookie_key[WIREGUARD_SESSION_KEY_LEN];
uint8_t label_mac1_key[WIREGUARD_SESSION_KEY_LEN];
// The last time we received a valid initiation message
uint32_t last_initiation_rx;
// The last time we sent an initiation message to this peer
uint32_t last_initiation_tx;
// last_tx and last_rx of data packets
uint32_t last_tx;
uint32_t last_rx;
// We set this flag on RX/TX of packets if we think that we should initiate a new handshake
bool send_handshake;
} wireguard_peer_t;
typedef struct wireguard_device {
// Maybe have a "Device private" member to abstract these?
struct netif *netif;
struct udp_pcb *udp_pcb;
struct netif *underlying_netif;
uint8_t public_key[WIREGUARD_PUBLIC_KEY_LEN];
uint8_t private_key[WIREGUARD_PRIVATE_KEY_LEN];
uint8_t cookie_secret[WIREGUARD_HASH_LEN];
uint32_t cookie_secret_millis;
// Precalculated
uint8_t label_cookie_key[WIREGUARD_SESSION_KEY_LEN];
uint8_t label_mac1_key[WIREGUARD_SESSION_KEY_LEN];
// List of peers associated with this device
struct wireguard_peer peers[WIREGUARD_MAX_PEERS];
bool valid;
} wireguard_device_t;
#define MESSAGE_INVALID 0
#define MESSAGE_HANDSHAKE_INITIATION 1
#define MESSAGE_HANDSHAKE_RESPONSE 2
#define MESSAGE_COOKIE_REPLY 3
#define MESSAGE_TRANSPORT_DATA 4
// 5.4.2 First Message: Initiator to Responder
typedef struct message_handshake_initiation {
uint8_t type;
uint8_t reserved[3];
uint32_t sender;
uint8_t ephemeral[32];
uint8_t enc_static[32 + WIREGUARD_AUTHTAG_LEN];
uint8_t enc_timestamp[WIREGUARD_TAI64N_LEN + WIREGUARD_AUTHTAG_LEN];
uint8_t mac1[WIREGUARD_COOKIE_LEN];
uint8_t mac2[WIREGUARD_COOKIE_LEN];
} __attribute__ ((__packed__)) message_handshake_initiation_t;
// 5.4.3 Second Message: Responder to Initiator
typedef struct message_handshake_response {
uint8_t type;
uint8_t reserved[3];
uint32_t sender;
uint32_t receiver;
uint8_t ephemeral[32];
uint8_t enc_empty[0 + WIREGUARD_AUTHTAG_LEN];
uint8_t mac1[WIREGUARD_COOKIE_LEN];
uint8_t mac2[WIREGUARD_COOKIE_LEN];
} __attribute__ ((__packed__)) message_handshake_response_t;
// 5.4.7 Under Load: Cookie Reply Message
typedef struct message_cookie_reply {
uint8_t type;
uint8_t reserved[3];
uint32_t receiver;
uint8_t nonce[COOKIE_NONCE_LEN];
uint8_t enc_cookie[WIREGUARD_COOKIE_LEN + WIREGUARD_AUTHTAG_LEN];
} __attribute__ ((__packed__)) message_cookie_reply_t;
// 5.4.6 Subsequent Messages: Transport Data Messages
typedef struct message_transport_data {
uint8_t type;
uint8_t reserved[3];
uint32_t receiver;
uint8_t counter[8];
// Followed by encrypted data
uint8_t enc_packet[];
} __attribute__ ((__packed__)) message_transport_data_t;
// Initialise the WireGuard system - need to call this before anything else
void wireguard_init(void);
bool wireguard_device_init(struct wireguard_device *device, const uint8_t *private_key);
bool wireguard_peer_init(struct wireguard_device *device, wireguard_peer_t *peer, const uint8_t *public_key, const uint8_t *preshared_key);
wireguard_peer_t *wireguard_peer_alloc(wireguard_device_t *device);
uint8_t wireguard_peer_index(struct wireguard_device *device, wireguard_peer_t *peer);
wireguard_peer_t *wireguard_peer_lookup_by_pubkey(struct wireguard_device *device, uint8_t *public_key);
wireguard_peer_t *wireguard_peer_lookup_by_peer_index(struct wireguard_device *device, uint8_t peer_index);
wireguard_peer_t *wireguard_peer_lookup_by_receiver(struct wireguard_device *device, uint32_t receiver);
wireguard_peer_t *wireguard_peer_lookup_by_handshake(struct wireguard_device *device, uint32_t receiver);
void wireguard_start_session(wireguard_peer_t *peer, bool initiator);
void keypair_update(wireguard_peer_t *peer, wireguard_keypair_t *received_keypair);
void keypair_destroy(wireguard_keypair_t *keypair);
wireguard_keypair_t *get_peer_keypair_for_idx(wireguard_peer_t *peer, uint32_t idx);
bool wireguard_check_replay(wireguard_keypair_t *keypair, uint64_t seq);
uint8_t wireguard_get_message_type(const uint8_t *data, size_t len);
wireguard_peer_t *wireguard_process_initiation_message(struct wireguard_device *device, struct message_handshake_initiation *msg);
bool wireguard_process_handshake_response(struct wireguard_device *device, wireguard_peer_t *peer, struct message_handshake_response *src);
bool wireguard_process_cookie_message(struct wireguard_device *device, wireguard_peer_t *peer, struct message_cookie_reply *src);
bool wireguard_create_handshake_initiation(struct wireguard_device *device, wireguard_peer_t *peer, struct message_handshake_initiation *dst);
bool wireguard_create_handshake_response(struct wireguard_device *device, wireguard_peer_t *peer, struct message_handshake_response *dst);
void wireguard_create_cookie_reply(struct wireguard_device *device, struct message_cookie_reply *dst, const uint8_t *mac1, uint32_t index, uint8_t *source_addr_port, size_t source_length);
bool wireguard_check_mac1(struct wireguard_device *device, const uint8_t *data, size_t len, const uint8_t *mac1);
bool wireguard_check_mac2(struct wireguard_device *device, const uint8_t *data, size_t len, uint8_t *source_addr_port, size_t source_length, const uint8_t *mac2);
bool wireguard_expired(uint32_t created_millis, uint32_t valid_seconds);
void wireguard_encrypt_packet(uint8_t *dst, const uint8_t *src, size_t src_len, wireguard_keypair_t *keypair);
bool wireguard_decrypt_packet(uint8_t *dst, const uint8_t *src, size_t src_len, uint64_t counter, wireguard_keypair_t *keypair);
#ifdef __cplusplus
}
#endif
#endif /* _WIREGUARD_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2021 Daniel Hope (www.floorsense.nz)
* Copyright (c) 2023 Simone Rossetto <simros85@gmail.com>
* Copyright (c) 2025 Stephan Hadinger
* 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. Neither the name of "Floorsense Ltd", "Agile Workspace Ltd" nor the names of
* its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
* Author: Daniel Hope <daniel.hope@smartalock.com>
*/
#ifndef _WIREGUARDIF_H_
#define _WIREGUARDIF_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_wireguard.h"
#include <time.h>
#include "lwip/arch.h"
#include "lwip/netif.h"
#include "lwip/ip_addr.h"
// Default MTU for WireGuard is 1420 bytes
#define WIREGUARDIF_MTU (1420)
#define WIREGUARDIF_DEFAULT_PORT (51820)
typedef struct wireguardif_init_data {
// Required: the private key of this WireGuard network interface
wg_key_t private_key2;
// Required: What UDP port to listen on
uint16_t listen_port;
// Optional: restrict send/receive of encapsulated WireGuard traffic to this network interface only (NULL to use routing table)
struct netif *bind_netif;
} wireguardif_init_data_t;
typedef struct wireguardif_peer {
wg_key_t public_key2;
// Optional pre-shared key (32 bytes) - make sure this is NULL if not to be used
wg_key_t preshared_key2;
// tai64n of largest timestamp we have seen during handshake to avoid replays
uint8_t greatest_timestamp[12];
// Allowed ip/netmask (can add additional later but at least one is required)
ip_addr_t allowed_ip;
ip_addr_t allowed_mask;
// End-point details (may be blank)
ip_addr_t endpoint_ip;
uint16_t endport_port;
uint16_t keep_alive;
} wireguardif_peer_t;
#define WIREGUARDIF_INVALID_INDEX (0xFF)
// Initialise a new WireGuard network interface (netif)
err_t wireguardif_init(struct netif *netif);
// Helper to initialise the peer struct with defaults
void wireguardif_peer_init(wireguardif_peer_t *peer);
// Add a new peer to the specified interface - see wireguard.h for maximum number of peers allowed
// On success the peer_index can be used to reference this peer in future function calls
err_t wireguardif_add_peer(struct netif *netif, wireguardif_peer_t *peer, u8_t *peer_index);
// Remove the given peer from the network interface
err_t wireguardif_remove_peer(struct netif *netif, u8_t peer_index);
// Try and connect to the given peer
err_t wireguardif_connect(struct netif *netif, u8_t peer_index);
// Stop trying to connect to the given peer
err_t wireguardif_disconnect(struct netif *netif, u8_t peer_index);
// Shutdown the WireGuard interface
void wireguardif_shutdown(struct netif *netif);
// Finalize the WireGuard interface after the netif is removed
void wireguardif_fini(struct netif *netif);
// Is the given peer "up"? A peer is up if it has a valid session key it can communicate with
err_t wireguardif_peer_is_up(struct netif *netif, u8_t peer_index, ip_addr_t *current_ip, u16_t *current_port);
// Get timestamp of latest handshake (with seconds resolution)
// Return 0 if no handshake already done or in case of errors
time_t wireguardif_latest_handshake(struct netif *netif, u8_t peer_index);
// Add ip/mask to the list of allowed ips of the given peer
err_t wireguardif_add_allowed_ip(struct netif *netif, u8_t peer_index, const ip_addr_t& ip, const ip_addr_t& mask);
#ifdef __cplusplus
}
#endif
#endif /* _WIREGUARDIF_H_ */

View File

@ -477,6 +477,10 @@
// #define USE_MQTT_AZURE_DPS_PRESHAREDKEY // OPTIONAL The Preshared Key of DPS https://github.com/tasmota/docs/blob/development/docs/Azure-IoT-Central.md
// #define USE_MQTT_AZURE_DPS_SCOPE_ENDPOINT // OPTIONAL Defaults to "https://global.azure-devices-provisioning.net/", can be changed for Azure China, Azure Germany or others.
// -- Wireguard VPN support ------------------------
// VPN support since v14.6.1
// #define USE_WIREGUARD // Enable Wireguard VPN support (ESP8266: +28k code +0.9k mem, ESP32: +22k code +0.9k mem)
// -- Telegram Protocol ---------------------------
//#define USE_TELEGRAM // Support for Telegram protocol (+49k code, +7.0k mem and +4.8k additional during connection handshake)
#define USE_TELEGRAM_FINGERPRINT "\x4E\x7F\xF5\x6D\x1E\x29\x40\x58\xAB\x84\xDE\x63\x69\x7B\xCD\xDF\x44\x2E\xD2\xF6" // Telegram api.telegram.org TLS public key fingerpring

View File

@ -0,0 +1,300 @@
/*
xdrv_80_wireguard_clientc.ino - creates a VPN connection to a Wireguard site
Copyright (C) 2024 Stephan Hadinger
This program 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.
This program 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 should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_WIREGUARD
#define XDRV_80 80
#include "esp_wireguard.h"
#include "IniFile.h"
#include "LList.h"
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
static const char WIREGUARD_CONF_FNAME[] = "/wireguard.conf";
static const char WIREGUARD_NETMASK[] = "0.0.0.0";
const char kWireGuardCommands[] PROGMEM = "WG|" // Prefix
"Connect|Stop";
void (* const WireGuardCommand[])(void) PROGMEM = {
&CmndWGConnect, &CmndWGStop };
typedef struct {
ip_addr_t addr;
ip_addr_t mask;
} allowed_ips_t;
struct Wireguard_t {
bool configured = false;
bool auto_connect = false;
bool started = false;
bool peer_up = false;
int8_t peer_status = -1; // known state: -1 unknown, 0 DOWN, 1 UP
uint32_t connected_since_utc = 0;
String endpoint;
LList<allowed_ips_t> allowed_ips;
// used by lib
wireguard_config_t config = {};
wireguard_ctx_t ctx = {0};
} Wireguard;
/*********************************************************************************************\
* WireGuard internal lower level functions
\*********************************************************************************************/
// WireguardLoadConfig
//
// Load configuration from INI file
// returns 'true' if succesful
bool WireguardLoadConfig(const char *filename) {
if (filename == NULL) { return false; }
if (!ffsp) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : WireGuard initialization failed, no file system"));
return false;
}
File file = ffsp->open(filename, "r");
if (!file) {
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : file '%s' not present, skipping"), filename);
return false;
}
IniFile ini(file);
bool valconf = true;
wireguard_config_t& config = Wireguard.config;
valconf = true;
valconf = valconf && ini.getValueBase64("Interface", "PrivateKey", config.private_key2, sizeof(config.private_key2));
valconf = valconf && ini.getCIDR("Interface", "Address", &config.address2, &config.subnet);
valconf = valconf && ini.getValueBase64("Peer", "PublicKey", config.public_key2, sizeof(config.public_key2));
valconf = valconf && ini.getValueBase64("Peer", "PresharedKey", config.preshared_key2, sizeof(config.preshared_key2));
valconf = valconf && ini.getDomainPort("Peer", "Endpoint", Wireguard.endpoint, Wireguard.config.port);
// read optional NetMask
ipaddr_aton(WIREGUARD_NETMASK, &config.netmask2);
ini.getIPAddress("Tasmota", "Netmask", &Wireguard.config.netmask2);
// read optional PersistentKeepalive
ini.getValueUInt16("Peer", "PersistentKeepalive", config.persistent_keepalive);
// read optional AutoConnect
ini.getValueBool("Tasmota", "AutoConnect", Wireguard.auto_connect);
// add allowedIPs
String allowed_ips_str;
ini.getValueString("Peer", "AllowedIPs", allowed_ips_str);
if (valconf) {
// read optional AllowedIPs
allowed_ips_t allowip;
while (allowed_ips_str.length() > 0) {
int32_t comma = allowed_ips_str.indexOf(",");
String cidr = (comma > 0) ? allowed_ips_str.substring(0, comma) : allowed_ips_str;
cidr.trim();
// AddLog(LOG_LEVEL_DEBUG, ">>>: allowed_ips_str '%s' comma %i cidr '%s'", allowed_ips_str.c_str(), comma, cidr.c_str());
if (IniFile::parseCIDR(cidr, &allowip.addr, &allowip.mask)) {
Wireguard.allowed_ips.addHead(allowip);
} else {
AddLog(LOG_LEVEL_INFO, PSTR("WG : Failed to parse allowed_ips '%s', skipping"), cidr.c_str());
}
if (comma > 0) {
allowed_ips_str = allowed_ips_str.substring(comma + 1);
} else {
break;
}
}
}
file.close();
if (!valconf) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : WireGuard initialization failed, invalid configuration"));
return false;
}
// now parse values
Wireguard.config.endpoint = Wireguard.endpoint.c_str();
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : reading '%s' address:%s/%s netmask:%s endpoint:%s:%i allowed_ips_str:'%s' PersistentKeepalive:%i"),
filename,
IPAddress(&config.address2).toString().c_str(), IPAddress(&config.subnet).toString().c_str(),
IPAddress(&config.netmask2).toString().c_str(),
Wireguard.config.endpoint, Wireguard.config.port, allowed_ips_str.c_str(),
config.persistent_keepalive);
return true;
}
// WireguardConnect
//
// Connect to peer
bool WireguardConnect(void) {
if (!Wireguard.configured || Wireguard.started) {
return false;
}
esp_err_t err = esp_wireguard_connect(&Wireguard.ctx);
if (err == ESP_OK) {
Wireguard.started = true;
for (const allowed_ips_t & allowedip : Wireguard.allowed_ips) {
err = esp_wireguard_add_allowed_ip(&Wireguard.ctx, allowedip.addr, allowedip.mask);
if (err != ESP_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("WG : Failed to add allowed_ips %_I/%_I, no space left"), allowedip.addr, allowedip.mask);
break;
}
AddLog(LOG_LEVEL_DEBUG, PSTR("WG : Added allowed_ips %_I/%_I"), allowedip.addr, allowedip.mask);
}
return true;
}
return false;
}
// WireguardStop
//
// Stop the current Wireguard connection
// Do nothing if there is no connection
void WireguardStop(void) {
if (!Wireguard.configured || !Wireguard.started) {
return;
}
// stop wireguard
esp_wireguard_disconnect(&Wireguard.ctx);
Wireguard.started = false;
AddLog(LOG_LEVEL_INFO, PSTR("WG : Wireguard peer DOWN"));
}
/*********************************************************************************************\
* WireGuard commands
\*********************************************************************************************/
// Initialize Wireguard client
void WireguardInit(void) {
if (WireguardLoadConfig(WIREGUARD_CONF_FNAME)) {
esp_wireguard_init(&Wireguard.config, &Wireguard.ctx);
Wireguard.configured = true;
} else {
Wireguard.configured = false;
}
}
// WireGuard Connect
void CmndWGConnect(void) {
if (!Wireguard.configured) {
ResponseCmndChar(PSTR("Not configured"));
return;
}
if (Wireguard.started) {
ResponseCmndChar(PSTR("Already started"));
return;
}
if (WireguardConnect()) {
ResponseCmndChar(PSTR("Success"));
} else {
ResponseCmndChar(PSTR("Failed"));
}
}
// WireGuard Stop
void CmndWGStop(void) {
if (!Wireguard.configured) {
ResponseCmndChar(PSTR("Not configured"));
return;
}
if (!Wireguard.started) {
ResponseCmndChar(PSTR("Not started"));
return;
}
// stop wireguard
Wireguard.auto_connect = false; // prevent auto-reconnect when we asked for a manual stop
WireguardStop();
ResponseCmndChar(PSTR("Success"));
}
// Loop every second
void WireguardLoop(void) {
if (Wireguard.started) {
esp_err_t err = esp_wireguard_peer_is_up(&Wireguard.ctx);
if (err == ESP_OK) {
if (Wireguard.peer_status != 1) {
Wireguard.peer_status = 1;
if (Rtc.utc_time >= START_VALID_TIME) { // record the connection time only if we have a valid time
Wireguard.connected_since_utc = Rtc.utc_time;
}
AddLog(LOG_LEVEL_INFO, PSTR("WG : Wireguard peer UP"));
}
// second chance, if connection happened with no time, and now time is valid
if (Wireguard.connected_since_utc == 0 && Rtc.utc_time >= START_VALID_TIME) {
Wireguard.connected_since_utc = Rtc.utc_time;
}
} else {
if (Wireguard.peer_status != 0) {
Wireguard.peer_status = 0;
AddLog(LOG_LEVEL_INFO, PSTR("WG : Wireguard peer DOWN"));
}
}
}
}
void WireguardNetworkUpDown(bool up) {
if (up) {
if (!Wireguard.started && Wireguard.auto_connect) {
WireguardConnect();
}
} else {
// Network is down
if (Wireguard.started) {
WireguardStop();
}
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv80(uint32_t function) {
bool result = false;
if (TasmotaGlobal.no_autoexec) { return result; } // do nothing in case of bootloop
switch (function) {
case FUNC_INIT:
WireguardInit();
break;
case FUNC_COMMAND:
result = DecodeCommand(kWireGuardCommands, WireGuardCommand);
break;
case FUNC_EVERY_SECOND:
WireguardLoop();
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_STATUS:
if (Wireguard.started && Wireguard.peer_status == 1) {
// number of seconds since connection, or -1 if no valid time
int32_t seconds = Wireguard.connected_since_utc ? Rtc.utc_time - Wireguard.connected_since_utc : -1;
WSContentStatusSticker(PSTR("VPN"), seconds);
}
break;
#endif // USE_WEBSERVER
case FUNC_NETWORK_UP:
WireguardNetworkUpDown(true);
break;
case FUNC_NETWORK_DOWN:
WireguardNetworkUpDown(false);
break;
}
return result;
}
#endif // USE_WIREGUARD