initial import from svn of 0.910a

This commit is contained in:
jrandom
2006-10-12 22:35:02 +00:00
commit 9f7ed247ea
231 changed files with 46333 additions and 0 deletions

103
CHANGES Normal file
View File

@ -0,0 +1,103 @@
[Version key: $major.$minor$type, $type: a(lpha), b(eta), r(elease)c(andidate)]
- bundled a gpg keyring for simplified patch verification
* 2006-10-08 0.910a released
- implemented the new "threads" command, allowing intra as well as
interchannel searching based on tags, with results ordered by most
recently updated threads.
- implemented the new "alias" command (and associated database table),
allowing single and multicommand aliases using ";" as a command
delimiter. e.g.
"alias sync menu syndicate; getindex; fetch; schedule --put archive; post"
* 2006-10-07 0.909a released
- added a new "history" and associated csh-style history editing
commands (!!, !-$num, !$num, ^a, ^a^b).
- simplify channel authorization management with new "listnyms",
"addnym", and "removenym" commands.
* 2006-10-06 0.908a released
- rework the database update process so we now run
src/syndie/db/ddl_update$n.txt whenever we are upgrading from database
schema version $n. This is backwards compatible
- revamp the message/metadata import process and db schema to allow
importing messages that are authorized/authenticated but cannot be read,
due to a missing key or passphrase. in turn, revamp the read/view menu
to display the undecrypted messages appropriately, prompting for the
valid decryption material, providable with the new "decrypt" command.
- encrypt private channel replies with the right key and scope
* 2006-10-05 0.907a released
- further thread rendering fixes, and support for a new "threadprev"
command.
- added some status and debug messages describing the progress of http
fetches, and added a timeout for reading http response headers (which
should come back quickly, even if there's lots of data to transfer)
- deal with passphrase encrypted messages in the bulkimport command by
renaming them to "pbe_msg_$num.syndie", since they require interaction
to import ("import --in $filename" displays the prompt for decryption,
and "import --in $filename --pass $passphrase" decrypts it).
* 2006-10-04 0.906a released
- added a new "prefs" command and related nymPref table, allowing you
to set persistent preferences for each nym to be loaded on login,
including debug and paginate state, the default http proxy, and a
default archive (allowing you to simply "getindex" without any
arguments). The database update is handled automatically when
necessary without any intervention.
- bugfix to the message thread rendering. it should now thread correctly
- rework the read menu to keep track of both the channels and the messages
being rendered so you can switch back and forth without regenerating a
channel list again -
(channels; messages --channel 2; view --message 0; messages --channel 3)
* 2006-10-03 0.905a released
- support "getindex --archive file:///some/path" as well as
"getindex --archive /some/path" and "getindex --archive http://some/url"
- removed some debug messages unintentionally left in
- added a new FAQ (thanks bar and void!)
* 2006-10-02 0.904a released
- further unauthorized post/reply improvements, but the changed
archive index-all.dat and index-new.dat have changed format, so
upgrading is mandatory (if you want to use http syndication ;)
- revamp the released packages:
syndie-$rev.bin.exe (java installer w/ launch4j'ed bin/syndie.exe)
syndie-$rev.bin.zip (no installer but w/ launch4j'ed bin/syndie.exe)
syndie-$rev.src.tar.bz2 (source only)
* 2006-09-30 0.903a released
- don't regenerate new channel read keys each time we update the
metadata (since they replace old ones, so we can't decrypt messages
that were wrapped with the old keys)
- improvements to the unauthorized post/reply processing, flagging
messages that weren't explicitly authorized by their keys but were
by their context as authorized (replies in channels that allow public
replies, posts in channels that allow public posts)
- small fixes to the syndie.bat and import.cgi
- fixes for goofy non-defensive-programming mistakes
- lots of bugfixes (thanks void and Complication)
* 2006-09-29 0.902a released
- deal with EOF on stdin (thanks bar!)
- both "?" and "help" now refer to the help command (thanks bar!)
- properly display blank lines in posts when appropriate
- fixed the archive index to include the right set of messages (oops)
- added the "builduri" helper commands for building Syndie URIs
of URLs, channels/messages/pages, and archives
* 2006-09-28 0.901a released
- added new "backup" and "restore" commands for simple database
archival and disaster recovery
- added new IzPack installer and launch4j wrapper
- support reading new posts from standard input ("addpage --in STDIN")
- use ".syndie" as the file extension instead of ".snd"
- display the pagination and debug toggle state after changes
(thanks Complication!)
- hide the automatic db init and registration output on first run,
as the information is saved in the database anyway
(thanks Complication!)
* 2006-09-26 0.900a released
- First baseline release

5
CREDITS Normal file
View File

@ -0,0 +1,5 @@
= Developers:
* jrandom <jrandom@i2p.net> - main dev
= Supporters:
* I2P contributors - http://www.i2p.net/halloffame

11
INSTALL Normal file
View File

@ -0,0 +1,11 @@
Syndie can be built from source with a modern (1.4+) java development
kit, or with a new GCJ (4.0+). You must have ant (1.6.5+), and the
source release includes HSQLDB (1.8.0.5), though newer revisions
should work.
To build a normal java instance, run "ant".
To run it without any installation, run "sh bin/syndie".
To build various packages, see the targets and instructions in
doc/web/dev.html

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Unless marked otherwise, all code and content making up this package
is released into the public domain. Code included in this package
must be under BSD or MIT compatible licenses.
HSQLDB (hsqldb.jar and servlet.jar) are licensed according to
lib/hsqldb_lic.txt (BSD-like)
The GNU-Crypto Fortuna PRNG and SHA-256 code is included with
modifications as documented (removing other GNU-Crypto dependencies
and renaming to avoid conflicts with standard GNU-Crypto code). These
files are located under src/gnu/crypto/ and are licensed according to
the GPL with the "linking exception". Since Syndie is not derived
from the referenced PRNG or hash functions, Syndie is not tainted.
The BouncyCastle HMAC and MD5 code is included with modifications
as documented (removing other BouncyCastle dependencies). These
files are located under src/org/bouncycastle/ and are licensed
according to the BouncyCastle license (an MIT-style license).

47
Makefile.mingw Normal file
View File

@ -0,0 +1,47 @@
#
#GCJ=/u1/mingw32-4.1.0/bin/mingw32-gcj
#GCJ=/u1/mingw32/bin/mingw32-gcj
#GCJ=/u1/mingw32-4.2-20060506/bin/i386-pc-mingw32-gcj
GCJ=gcj
EXECUTABLE=syndie.exe
OPTIMIZE=-O2
GCJFLAGS=-g ${OPTIMIZE} -fjni -Wall
DLLFLAGS=-shared -Wl,--kill-at -Wall
I2P_JAR=lib/i2p.jar
SYNDIE_JAR=lib/syndie.jar
SERVLET_JAR=lib/servlet.jar
all: ${EXECUTABLE}
hsqldb_gcj.jar:
make -f Makefile.nix hsqldb_gcj.jar
hsqldb_gcj.o: hsqldb_gcj.jar
${GCJ} ${GCJFLAGS} -c -o hsqldb_gcj.o ${SERVLET_JAR} hsqldb_gcj.jar
clean:
@rm -f ${SYNDIE_JAR} syndie.o
@rm -f ${EXECUTABLE}
distclean: clean
@rm -f hsqldb_gcj.o i2p.o
@rm -rf hsql hsqldb_gcj.jar
@ant clean
@rm -rf logs
${SYNDIE_JAR}:
@echo "Compiling syndie"
ant -q jar
syndie.o: ${SYNDIE_JAR}
${GCJ} ${GCJFLAGS} -c --classpath=${I2P_JAR} ${SYNDIE_JAR}
${EXECUTABLE}: hsqldb_gcj.o syndie.o
${GCJ} -g -o ${EXECUTABLE} \
-Lwin32 -o ${EXECUTABLE} \
-Wl,--enable-runtime-pseudo-reloc \
--main=syndie.db.TextUI -mwindows hsqldb_gcj.o syndie.o
test:
${GCJ} ${GCJFLAGS} -static-libgcj -o t.exe --main=t t.java

60
Makefile.mingw.static Normal file
View File

@ -0,0 +1,60 @@
#
#GCJ=/u1/mingw32-4.1.0/bin/mingw32-gcj
#GCJ=/u1/mingw32/bin/mingw32-gcj
GCJ=/u1/mingw32-4.2-20060506/bin/i386-pc-mingw32-gcj
EXECUTABLE=syndie.exe
OPTIMIZE=-O2
GCJFLAGS=-g ${OPTIMIZE} -fjni -Wall
DLLFLAGS=-shared -Wl,--kill-at -Wall
I2P_JAR=lib/i2p.jar
SYNDIE_JAR=lib/syndie.jar
SERVLET_JAR=lib/servlet.jar
all: ${EXECUTABLE}
hsqldb_gcj.jar:
@make -f Makefile.nix hsqldb_gcj.jar
hsqldb_gcj.o: hsqldb_gcj.jar
${GCJ} ${GCJFLAGS} -c -o hsqldb_gcj.o ${SERVLET_JAR} hsqldb_gcj.jar
hsqldb_gcj.dll: hsqldb_gcj.o
${GCJ} ${DLLFLAGS} -o hsqldb_gcj.dll hsqldb_gcj.o
i2p.o: ${I2P_JAR}
${GCJ} ${GCJFLAGS} -c -o i2p.o ${I2P_JAR}
i2p.dll: i2p.o
${GCJ} ${DLLFLAGS} -o i2p.dll i2p.o
clean:
@rm -f ${SYNDIE_JAR} syndie.o syndie.dll
@rm -f ${EXECUTABLE} syndie.o syndie.dll
distclean: clean
@rm -f hsqldb_gcj.o i2p.o
@rm -f hsqldb_gcj.dll i2p.dll
@rm -rf hsql hsqldb_gcj.jar
@ant clean
@rm -rf logs
${SYNDIE_JAR}:
@echo "Compiling syndie"
@ant -q jar
syndie.o: ${SYNDIE_JAR}
@${GCJ} ${GCJFLAGS} -c --classpath=${I2P_JAR} ${SYNDIE_JAR}
syndie.dll: syndie.o
${GCJ} ${DLLFLAGS} -L. -lhsqldb_gcj -li2p -o syndie.dll syndie.o
${EXECUTABLE}: hsqldb_gcj.dll i2p.dll syndie.dll
${GCJ} -g -o ${EXECUTABLE} \
-L. -L${SWT_LIBDIR} -Lwin32 \
-lhsqldb_gcj -li2p -lsyndie -o ${EXECUTABLE} \
-static-libgcj -mwindows \
--main=syndie.db.TextUI
test:
${GCJ} ${GCJFLAGS} -static-libgcj -o t.exe --main=t t.java

59
Makefile.nix Normal file
View File

@ -0,0 +1,59 @@
#
# $os version:
GCJ=gcj
#GCJ=/u1/gcc-4.2-20060520-x86_64/bin/gcj
EXECUTABLE=syndie
GCJFLAGS=-g -O2 -fPIC -fjni -Wall
all: syndie
hsqldb_gcj.jar:
@rm -rf hsql
@mkdir hsql
@echo "building GCJ-friendly hsqldb_gcj.jar"
@(cd hsql ;\
jar xf ../lib/hsqldb.jar ;\
rm -f org/hsqldb/util/DatabaseManager*.class \
org/hsqldb/util/TableSorter*.class \
org/hsqldb/HsqlSocketFactorySecure.class \
org/hsqldb/persist/NIOLockFile.class \
org/hsqldb/util/*Swing*.class ;\
jar cfm ../hsqldb_gcj.jar META-INF/MANIFEST.MF *class org ;\
)
hsqldb_gcj.o: hsqldb_gcj.jar
${GCJ} ${GCJFLAGS} -c -o hsqldb_gcj.o lib/servlet.jar hsqldb_gcj.jar
clean:
@rm -f lib/syndie.jar syndie.o
@rm -f ${EXECUTABLE} syndie.o
distclean: clean
@rm -f hsqldb_gcj.o
@rm -rf hsql hsqldb_gcj.jar
@rm -rf syndie-dev
@rm -f syndie-dev.tar.bz2
@ant distclean
@rm -rf logs
lib/syndie.jar:
@echo "Compiling syndie"
@ant -q jar
syndie.o: lib/syndie.jar
${GCJ} ${GCJFLAGS} -c lib/syndie.jar
${EXECUTABLE}: hsqldb_gcj.o syndie.o
${GCJ} ${GCJFLAGS} -o ${EXECUTABLE} --main=syndie.db.TextUI hsqldb_gcj.o syndie.o
package: ${EXECUTABLE}
@ant -q prep-java
@rm -f syndie-?.????/bin/syndie
@rm -f syndie-?.????/bin/syndie.bat
@rm -f syndie-?.????/lib/*jar
@strip ${EXECUTABLE}
@cp ${EXECUTABLE} syndie-?.????/bin/
@mkdir -p doc/web/dist
@tar cjf doc/web/dist/syndie-native.tar.bz2 syndie-?.????
@rm -rf syndie-?.????
@echo "Native package built into doc/web/dist/syndie-native.tar.bz2"

22
README Normal file
View File

@ -0,0 +1,22 @@
This is Syndie, an anonymity-aware distributed forum.
Up to date information about Syndie can be found over at
http://syndie.i2p.net/
This package contains:
- INSTALL: how to build and install Syndie from source
- CHANGES: change log for Syndie
- CREDITS: who has contributed to the Syndie development effort
- LICENSE: license information for the Syndie codebase
- doc/syndie.1: unix style manual page for the syndie command
- doc/web/: HTML documentation for Syndie
- bin/: contains the main Syndie command, sample Syndie scripts,
and an example perl CGI for operating an HTTP archive
- lib/: compiled java libraries
- resources/: localized data, etc
- src/: source code
- Makefile.nix: makefile for building syndie with GCJ for the current platform
- Makefile.mingw: build a native windows executable with mingw/GCJ from *nix
- build.xml: build the java code
- nbproject/: developer setup for NetBeans users

41
TODO Normal file
View File

@ -0,0 +1,41 @@
Key:
- pending
X recently completed
(longer term tasks are included in doc/web/roadmap.html)
===
- push:
X doc/web/ onto syndie.i2p.net (~/syndie-inst/doc/web/)
X archive onto http://syndie.i2p.net/archive/ (~/.syndie/archive/)
X dist files ont http://syndie.i2p.net/dist/
- darcs migration
- public archive online
- syndie-darcs@i2p.net mailing list
X syndie-announce@i2p.net mailing list
X use darcs internally
===maybe:
- internationalize the text UI
- simple gui reader app?
- logo, screenshots
- submit hsqldb patch for gcj/etc that has java.nio.* but they're
unimplemented (so it can check an env flag to see if it should Class.forName
it)
- archive cleanup script (delete expired posts / channels / etc, trim by size / age)
- add a "forward [--private $boolean] --to $chan" command
- detect application/x-syndie attachments (and prompt to import)
- GCJ/MinGW'ed install for syndie-$rev.win.exe (~= syndie-$rev.bin.exe with a native syndie.exe)
- doc up some interesting scenarios for channels
- multiauthor private w/ visible TargetChannel (easy to fetch, but exposes message
count for a channel)
- multiauthor private w/ no visible TargetChannel (need to be in the channel to know
what channels to pull from to get the messages to decrypt)
- singleauthor private
- channelReadKey rotation
- readKey delivery only in pbe'd msgs
- let channels specify message sizes (<$xKB, ==$xKB, =={$x,$y,$y}KB) so every
message in the channel looks the same (.syndie format supports arbitrary padding)
- hashcash(msgURI) in the public headers
- allows someone to precalc the hashcash for a particular uri then give that calc'ed
value to someone authorized to post under the msgURI's scope, so low-cpu users can
still post to a forum if they're given a list of available messageId/hashcash values
(assuming they're authorized to post in the uri's scope, of course)

1
VERSION Normal file
View File

@ -0,0 +1 @@
syndie.version=0.910a

6
bin/bulkimport.syndie Executable file
View File

@ -0,0 +1,6 @@
#!syndie
login
menu syndicate
bulkimport --dir /tmp/cgiImport --delete true
buildindex
exit

6
bin/httpgetdist.syndie Normal file
View File

@ -0,0 +1,6 @@
login
menu syndicate
getindex --archive http://syndie.i2p.net/archive/ --scope new
fetch --style diff
buildindex
exit

106
bin/import.cgi Normal file
View File

@ -0,0 +1,106 @@
#!/usr/bin/perl -w
use strict;
use CGI qw(-debug);
$| = 1; # flush, goddamn you
$CGI::POST_MAX = 4*1024*1024; # 4MB max across all uploaded messages
my($requiredPassphrase) = undef; # set to a string to require authentication to import
my($uploadDir) = '/tmp/cgiImport';
if (! -e $uploadDir) {
mkdir $uploadDir;
}
#$ENV{TMPDIR} = $uploadDir; # CGI.pm stores temporary files here
my($query);
$query = new CGI; # actually parses & caches the data here
if (defined $requiredPassphrase) {
if ($requiredPassphrase eq $query->param('pass')) {
# authenticated
} else {
print $query->header('text/plain','403 Not authorized');
exit;
}
}
print $query->header('text/plain');
my($num) = 0;
# pull in the meta
my($metaIndex) = 0;
my($moreMeta) = 1;
while ($moreMeta) {
my($metaFile) = $query->upload("meta$metaIndex");
if (!$metaFile) {
$moreMeta = 0;
} else {
$num = 0;
my($metaFilename) = "";
while ($num >= 0) {
$metaFilename = $uploadDir;
$metaFilename .= '/meta';
$metaFilename .= '_';
$metaFilename .= $num;
$metaFilename .= '_';
$metaFilename .= $metaIndex;
$metaFilename .= '.syndie';
if (-e $metaFilename) {
$num = $num + 1;
} else {
$num = -1; # aka break
}
}
open META, ">$metaFilename";
binmode META;
while (<$metaFile>) { print META; }
close META;
chmod 666, $metaFilename; # so it can be deleted by whomever
# print STDOUT "Uploaded to $metaFilename\n";
$metaIndex = $metaIndex + 1;
}
}
# now pull in the posts
my($postIndex) = 0;
my($morePost) = 1;
while ($morePost) {
my($postFile) = $query->upload("post$postIndex");
if (!$postFile) {
$morePost = 0;
} else {
$num = 0;
my($postFilename) = "";
while ($num >= 0) {
#$postFilename = $uploadDir . '/post_' . $num . '_' . $postIndex . '.syndie';
$postFilename = $uploadDir;
$postFilename .= '/post';
$postFilename .= '_';
$postFilename .= $num;
$postFilename .= '_';
$postFilename .= $postIndex;
$postFilename .= '.syndie';
if (-e $postFilename) {
$num = $num + 1;
} else {
$num = -1; # aka break
}
}
open POST, ">$postFilename";
binmode POST;
while (<$postFile>) { print POST; }
close POST;
chmod 666, $postFilename; # so it can be deleted by whomever
# print STDOUT "Uploaded to $metaFilename\n";
# print STDOUT "Uploaded to $postFilename\n";
$postIndex = $postIndex + 1;
}
}
print STDOUT 'Uploaded ' . $postIndex . ' posts and ' . $metaIndex . " metadata\n";
if ($postIndex == 0 && $metaIndex == 0) {
print STDOUT "No files updated. query: \n";
print STDOUT $query->Vars;
}
1;

9
bin/syndie Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
JAVA=java
#JAVA=/opt/kaffe/bin/kaffe
#JAVA=/usr/local/kaffe117/bin/kaffe
#JAVA=/u1/gcc-4.2-20060520-x86_64/bin/gij
LOC=`which $0`
SYNDIEBINDIR=`dirname ${LOC}`
SYNDIELIBDIR=${SYNDIEBINDIR}/../lib
$JAVA -cp ${SYNDIELIBDIR}/syndie.jar:${SYNDIELIBDIR}/hsqldb.jar syndie.db.TextUI $@

6
bin/syndie.bat Executable file
View File

@ -0,0 +1,6 @@
@ECHO OFF
SET JAVA=java
REM SET JAVA=/opt/kaffe/bin/kaffe
REM SET JAVA=/usr/local/kaffe117/bin/kaffe
REM SET JAVA=/u1/gcc-4.2-20060520-x86_64/bin/gij
%JAVA% -cp lib\syndie.jar;lib\hsqldb.jar syndie.db.TextUI %*

69
build-before-profiler.xml Normal file
View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<project name="syndie" default="default" basedir=".">
<description>Builds, tests, and runs the project syndie.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar-with-manifest: JAR building (if you are using a manifest)
-do-jar-without-manifest: JAR building (if you are not using a manifest)
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="syndie-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>

302
build.xml Normal file
View File

@ -0,0 +1,302 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<project name="syndie" default="default" basedir=".">
<description>Builds, tests, and runs the project syndie.</description>
<import file="nbproject/build-impl.xml"/>
<import file="nbproject/profiler-build-impl.xml"/> <!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar-with-manifest: JAR building (if you are using a manifest)
-do-jar-without-manifest: JAR building (if you are not using a manifest)
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="syndie-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
<!-- override the default so we don't build the javadoc unless requested -->
<target name="default" depends="jar" description="Build the whole project."/>
<target name="-pre-compile">
<loadproperties srcfile="VERSION" />
<echo file="src/syndie/Version.java">package syndie;
public class Version {
public static final String VERSION = "${syndie.version}";
}
</echo>
</target>
<target name="import-i2p-source" description="Import the segments of i2p's SDK that we require.">
<copy todir="src">
<fileset dir="../i2p/core/java/src/" excludes="org/xlattice/**,net/i2p/client/*.java,net/i2p/client/datagram/**,net/i2p/data/Lease*,net/i2p/data/Payload*,net/i2p/data/Router*,net/i2p/data/TunnelId*,net/i2p/data/i2cp/**,net/i2p/util/ShellCommand*,net/i2p/util/FileUtil*,net/i2p/util/Exec.java,net/i2p/util/Delete.java,net/i2p/util/Copy.java,net/i2p/util/DecayingBloomFilter*,net/i2p/util/HTTPSendData*" includes="**/*java" />
</copy>
</target>
<target name="distclean" depends="clean">
<delete dir="doc/web/dist/" />
</target>
<target name="-post-clean">
<loadproperties srcfile="VERSION" />
<delete file="lib/syndie.jar" />
<delete dir="syndie-${syndie.version}" />
<delete file="syndie-${syndie.version}.bin.zip" />
<delete file="syndie-${syndie.version}.bin-noexe.zip" />
<delete file="syndie-${syndie.version}.src.tar.bz2" />
<delete file="src/syndie/Version.java" />
<delete file="debug.log" />
<delete file="syndie-${syndie.version}.bin.jar" />
<delete file="syndie-${syndie.version}.bin.exe" />
<delete file="syndie-${syndie.version}.exe" />
<delete file="syndie-${syndie.version}.jar" />
<delete dir="logs" />
<delete dir="build" />
</target>
<target name="java-package" depends="clean, prep-java" description="Package up syndie-$version.bin.zip, without including bin/syndie.exe">
<loadproperties srcfile="VERSION" />
<mkdir dir="doc/web/dist" />
<zip destfile="doc/web/dist/syndie-${syndie.version}.bin-noexe.zip">
<zipfileset dir="." includes="syndie-${syndie.version}/**" />
</zip>
<delete dir="syndie-${syndie.version}" />
<ant target="clean" />
</target>
<target name="java-package-exe" depends="clean, prep-java, syndie-exe" description="Package up syndie-$version.bin.zip, including bin/syndie.exe">
<loadproperties srcfile="VERSION" />
<mkdir dir="doc/web/dist" />
<zip destfile="doc/web/dist/syndie-${syndie.version}.bin.zip">
<zipfileset dir="." includes="syndie-${syndie.version}/**" />
</zip>
<delete dir="syndie-${syndie.version}" />
</target>
<target name="prep-java" depends="jar">
<loadproperties srcfile="VERSION" />
<delete dir="syndie-${syndie.version}" />
<mkdir dir="syndie-${syndie.version}" />
<mkdir dir="syndie-${syndie.version}/bin" />
<mkdir dir="syndie-${syndie.version}/doc" />
<mkdir dir="syndie-${syndie.version}/doc/web" />
<mkdir dir="syndie-${syndie.version}/lib" />
<copy todir="syndie-${syndie.version}/doc/">
<fileset dir="." includes="CHANGES, CREDITS, INSTALL, LICENSE, README, TODO" />
</copy>
<copy todir="syndie-${syndie.version}/" file="resources/welcome.txt" />
<copy todir="syndie-${syndie.version}/doc/" file="doc/syndie.1" />
<copy todir="syndie-${syndie.version}/doc/web/">
<fileset dir="doc/web/" includes="*" />
</copy>
<copy todir="syndie-${syndie.version}/bin/">
<fileset dir="bin/" includes="*" />
</copy>
<copy todir="syndie-${syndie.version}/lib/">
<fileset dir="lib/" includes="*" />
</copy>
</target>
<target name="source-package" depends="clean" description="Package up syndie-$version.src.tar.bz2">
<loadproperties srcfile="VERSION" />
<mkdir dir="doc/web/dist" />
<tar compression="bzip2" destfile="doc/web/dist/syndie-${syndie.version}.src.tar.bz2">
<tarfileset dir="." includes="**" excludes=".svn, *.bz2, *.exe, doc/web/dist/**, nbproject/private/**" prefix="syndie-${syndie.version}" />
</tar>
</target>
<target name="installer-exe" depends="prep-java, syndie-exe, -do-installer" description="Package up syndie-$version.bin.exe">
<echo>Using Launch4J packager in ${launch4jdir}/launch4j.jar
(If located elsewhere, run "ant -Dlaunch4jdir=/some/path installer-exe")</echo>
<loadproperties srcfile="VERSION" />
<mkdir dir="doc/web/dist" />
<taskdef name="launch4j" classpath="${launch4jdir}/launch4j.jar:${launch4jdir}/lib/xstream.jar"
classname="net.sf.launch4j.ant.Launch4jTask" />
<echo file="launchinstall.xml"><![CDATA[
<launch4jConfig>
<headerType>gui</headerType>
<jar>syndie-${syndie.version}.bin.jar</jar>
<outfile>doc/web/dist/syndie-${syndie.version}.bin.exe</outfile>
<errTitle>Syndie</errTitle>
<chdir>.</chdir>
<jre><minVersion>1.4.0</minVersion></jre>
<versionInfo>
<fileVersion>0.0.0.0</fileVersion>
<productVersion>0.0.0.0</productVersion>
<txtFileVersion>${syndie.version}</txtFileVersion>
<txtProductVersion>${syndie.version}</txtProductVersion>
<fileDescription>Syndie ${syndie.version} installer</fileDescription>
<copyright>copyright is theft</copyright>
<productName>Syndie ${syndie.version}</productName>
<internalName>syndie</internalName>
<originalFilename>syndie-${syndie.version}.bin.exe</originalFilename>
</versionInfo>
</launch4jConfig>
]]></echo>
<launch4j configFile="launchinstall.xml" />
<delete file="launchinstall.xml" />
<delete file="syndie-${syndie.version}.bin.jar" />
</target>
<target name="installer" depends="prep-java, -do-installer" description="Package up syndie-$version.bin.jar" />
<target name="-do-installer" description="Package up syndie-$version.bin.jar">
<echo>Using IzPack installer in ${izpackdir}/lib/compiler.jar
(If located elsewhere, run "ant -Dizpackdir=/some/path installer")</echo>
<loadproperties srcfile="VERSION" />
<taskdef name="izpack" classpath="${izpackdir}/lib/standalone-compiler.jar"
classname="com.izforge.izpack.ant.IzPackTask" />
<izpack output="syndie-${syndie.version}.bin.jar"
installerType="standard"
basedir="."
izPackDir="${izpackdir}/">
<config><![CDATA[
<installation version="1.0">
<info>
<appname>Syndie</appname>
<appversion>@{syndie.version}</appversion>
<appsubpath>syndie-@{syndie.version}</appsubpath>
<authors>
<author name="Syndie development team" email="syndie-inst@i2p.net"/>
</authors>
<url>http://syndie.i2p.net</url>
<javaversion>1.4</javaversion>
<summarylogfilepath>$INSTALL_PATH/install.log</summarylogfilepath>
</info>
<variables>
<variable name="desktopshortcutcheckboxenabled" value="true" />
</variables>
<guiprefs width="700" height="500" resizable="yes" />
<locale>
<langpack iso3="eng"/>
</locale>
<native type="izpack" name="ShellLink.dll" />
<resources>
<res id="shortcutSpec.xml" src="resources/Win_shortcutSpec.xml" />
<res id="Unix_shortcutSpec.xml" src="resources/Unix_shortcutSpec.xml" />
</resources>
<panels>
<panel classname="HelloPanel"/>
<panel classname="TargetPanel"/>
<panel classname="SummaryPanel"/>
<panel classname="InstallPanel"/>
<panel classname="ShortcutPanel"/>
<panel classname="SimpleFinishPanel"/>
</panels>
<packs>
<pack name="Base" required="yes">
<description>Base installation files</description>
<fileset dir="syndie-@{syndie.version}" includes="**/*" excludes="bin/syndie.bat,bin/syndie" targetdir="$INSTALL_PATH"/>
<singlefile src="resources/syndie.bat" target="$INSTALL_PATH/bin/syndie.bat" os="windows" override="true" />
<singlefile src="resources/syndie" target="$INSTALL_PATH/bin/syndie" os="unix" override="true" />
<singlefile src="resources/syndie" target="$INSTALL_PATH/bin/syndie" os="mac" override="true" />
<parsable targetfile="$INSTALL_PATH/bin/syndie"><os family="unix" /></parsable>
<parsable targetfile="$INSTALL_PATH/bin/syndie.bat"><os family="windows" /></parsable>
<parsable targetfile="$INSTALL_PATH/bin/syndie"><os family="mac" /></parsable>
<!-- postinstall stuff for *nix -->
<!-- stage=never means chmod a+x -->
<executable targetfile="$INSTALL_PATH/bin/syndie" type="bin" stage="never" keep="true" failure="warn"><os family="unix" /></executable>
<executable targetfile="$INSTALL_PATH/bin/syndie.exe" type="bin" stage="never" keep="true" failure="warn"><os family="windows" /></executable>
<executable targetfile="$INSTALL_PATH/bin/syndie.bat" type="bin" stage="never" keep="true" failure="warn"><os family="windows" /></executable>
<executable targetfile="$INSTALL_PATH/bin/syndie" type="bin" stage="never" keep="true" failure="warn"><os family="mac" /></executable>
</pack>
</packs>
</installation>
]]></config>
</izpack>
<delete dir="syndie-${syndie.version}" />
</target>
<target name="syndie-exe" depends="jar" description="build a syndie.exe">
<echo>Using Launch4J packager in ${launch4jdir}/launch4j.jar
(If located elsewhere, run "ant -Dlaunch4jdir=/some/path syndie-exe")</echo>
<loadproperties srcfile="VERSION" />
<mkdir dir="doc/web/dist" />
<taskdef name="launch4j" classpath="${launch4jdir}/launch4j.jar:${launch4jdir}/lib/xstream.jar"
classname="net.sf.launch4j.ant.Launch4jTask" />
<jar jarfile="syndie-${syndie.version}.jar">
<manifest>
<attribute name="Main-Class" value="syndie.db.TextUI" />
<!-- relative to the exe/jar file -->
<attribute name="Class-Path" value="../lib/syndie.jar ../lib/hsqldb.jar ../lib/servlet.jar" />
</manifest>
</jar>
<echo file="launchsyndie.xml"><![CDATA[
<launch4jConfig>
<headerType>console</headerType>
<jar>syndie-${syndie.version}.jar</jar>
<outfile>syndie-${syndie.version}.exe</outfile>
<errTitle>Syndie</errTitle>
<chdir>..</chdir>
<jre><minVersion>1.4.0</minVersion></jre>
<versionInfo>
<fileVersion>0.0.0.0</fileVersion>
<productVersion>0.0.0.0</productVersion>
<txtFileVersion>${syndie.version}</txtFileVersion>
<txtProductVersion>${syndie.version}</txtProductVersion>
<fileDescription>Syndie ${syndie.version} text interface</fileDescription>
<copyright>copyright is theft</copyright>
<productName>Syndie ${syndie.version}</productName>
<internalName>syndie</internalName>
<originalFilename>syndie-${syndie.version}.exe</originalFilename>
</versionInfo>
</launch4jConfig>
]]></echo>
<launch4j configFile="launchsyndie.xml" />
<delete file="launchsyndie.xml" />
<delete file="syndie-${syndie.version}.jar" />
<copy tofile="syndie-${syndie.version}/bin/syndie.exe" file="syndie-${syndie.version}.exe" />
</target>
<target name="dist">
<ant target="distclean" />
<ant target="source-package" />
<ant target="installer-exe" />
<ant target="java-package-exe" />
<ant target="java-package" />
</target>
</project>

29
doc/syndie.1 Normal file
View File

@ -0,0 +1,29 @@
.TH SYNDIE "1" "October 2006" "syndie" "User Commands"
.SH NAME
syndie \- interact with the Syndie distributed forum
.SH SYNOPSIS
.B syndie
[\fI@script_path\fR] [\fIdata_path\fR]
.br
.SH DESCRIPTION
.PP
Launch the text-only scriptable Syndie interface.
.TP
\fBscript_path\fR
Run the given script
.TP
\fBdata_path\fR
Use the given directory as the root Syndie path for
loading the archive, database, etc. If not specified,
Syndie uses ${HOME}/.syndie/ as the root path.
.SH AUTHOR
Written by jrandom
.SH "REPORTING BUGS"
Report bugs to <syndie-dev@i2p.net>.
.SH COPYRIGHT
Copyright is theft. Syndie is in the public domain.
.br
.SH "SEE ALSO"
Syndie's documentation is primarily in HTML, located in the
Syndie installation directory or
.B http://syndie.i2p.net/

35
doc/web/about.html Normal file
View File

@ -0,0 +1,35 @@
<html>
<head>
<title>Syndie: About</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<b>[about]</b>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
Syndie is being developed by an open group of volunteers, led by
jrandom. All contributions are welcome, be they <a href="dev.html">code</a>,
content, or <a href="donate.html">cash</a>.
<p>Syndie's code is entirely open source - unless otherwise specified, all
code and content contained in the Syndie releases are put out into the
public domain.</p>
</div>
</body>
</html>

163
doc/web/archive.html Normal file
View File

@ -0,0 +1,163 @@
<html>
<head>
<title>Syndie: archives</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<b>[dev]</b>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<div class="leftnav">
<a href="dev.html">[code]</a><br />
<a href="spec.html">[spec]</a><br />
<b>[archive]</b><br />
<a href="db.html">[database]</a><br />
</div>
<div class="bodycontent">
Each Syndie instance operates with its own archive of messages, storing
the data extracted from the <a href="spec.html#message">signed messages</a>
within its local <a href="db.html">database</a> (by default stored under
<code>$dataRoot/db/</code>), an archive of those signed messages in the
<code>$dataRoot/archive/</code> file hierarchy, and an archive of locally
created but not yet distributed messages in the
<code>$dataRoot/outbound/</code> file hierarchy. The contents of
<code>$dataRoot/archive/</code> can be wiped out without any loss of
functionality, though doing so prevents the Syndie instance from sharing
the authenticated messages with other people.
<p>Within the <code>$dataRoot/archive/</code> directory, each channel
has its own subdirectory containing the channel's metadata (in <code>meta.syndie</code>)
and posts (in <code>$messageId.syndie</code>). In addition, there are three
index files for each channel (<code>index-all.dat, index-new.dat,
index-unauthorized.dat</code>) as well as four index files for the
entire archive itself (<code>index-all.dat, index-meta.dat, index-new.dat,
index-unauthorized.dat</code>). These indexes are rebuilt with the
<a href="manual.html#syndicate_buildindex">buildindex</a> command, summarizing all posts
in the channel/archive (<code>index-all.dat</code>
and <code>$channelHash/index-all.dat</code>), all posts received or published
in the last few days (<code>index-new.dat</code> and
<code>$channelHash/index-new.dat</code>),
the metadata editions of all known channels (<code>index-meta.dat</code>),
and all posts for individual
channels that are not authorized (<code>index-unauthorized.dat</code> and
<code>$channelHash/index-unauthorized.dat</code>).</p>
<p>The index files all use the following format:</p><pre>
$channelHash // 32 byte SHA256 value
$channelEdition // 4 byte unsigned integer
$reciveDate // 4 byte unsigned integer - days since 1970/1/1
$metaFileSize // 4 byte unsigned integer - size of the meta.syndie file
$numMessages // 4 byte unsigned integer - how many messages are known
$indexedMessages // 4 byte unsigned integer - # messages following
for (i = 0; i < $indexedMessages; i++)
$messageId // 8 byte unsigned integer
$receiveDate // 4 byte unsigned integer - days since 1970/1/1
$entryFileSize // 4 byte unsigned integer - size of $messageId.syndie
$flags // 1 byte.
// 1<<7: authorized
// 1<<6: private reply
// 1<<5: password based encrypted
// 1<<4: archive considers the post "new"
// external chan refs in index-all and index-new refer to posts that
// are in another scope but both target this $channelHash scope and
// are authorized. unsigned chan refs in index-unauthorized are the
// same, but not authorized
$externalChanRefs // 4 byte unsigned integer - # channels following
for (i = 0; i < $externalChanRefs; i++)
$scopeHash // 32 byte SHA256 value that the post is in
$posts // 4 byte unsigned integer - messages following
for (j = 0; j < $posts; j++
$messageId // 8 byte unsigned integer
$receiveDate // 4 byte unsigned integer - days since 1970/1/1
$entrySize // 4 byte unsigned integer - sizeof .syndie file
$flags // 1 byte.
// 1<<7: authorized
// 1<<6: private reply
// 1<<5: password based encrypted
// 1<<4: archive considers the post "new"
</pre>
<p>Individual posts are found under
<code>$dataRoot/archive/$scope/$messageId.syndie</code>, and metadata under
<code>$dataRoot/archive/$scope/meta.syndie</code>. The externally referenced
posts are found under their original scope path, not the targetted channel
path - <code>$dataRoot/archive/$scopeHash/$messageId.syndie</code> and not
<code>$dataRoot/archive/$channelHash/$messageId.syndie</code></p>
<p>The <code>$dataRoot/archive/index-*dat</code> files simply concatenate the
<code>$dataRoot/archive/$channelHash/index-*.dat</code> files together.
These file formats are implemented in the <code>syndie.db.ArchiveIndex</code>
class, and are subject to change.</p>
<p>Given the simple file-based archive hierarchy and index, running a public
Syndie archive is trivial - simply publish your <code>$dataRoot/archive/</code>
in a webserver and tell people the URL. They will then be able to load up
their Syndie instance and use the <a href="manual.html#getindex">getindex and fetch</a>
commands to pull posts from the archive into their local Syndie instance.</p>
<p>To enable people to upload posts to an HTTP archive, Syndie bundles an
<code>import.cgi</code> CGI script - simply place the <code>import.cgi</code>
in the <code>$dataRoot/archive/</code> directory, mark it as executable and
tell your webserver to run <code>.cgi</code> files as CGI scripts. For example,
the following Apache httpd.conf directives would suffice (assuming your login
was <code>jrandom</code>):</p><pre>
&lt;Directory /home/jrandom/.syndie/archive/&gt;
Options ExecCGI Indexes
&lt;/Directory&gt;
Alias /archive/ "/home/jrandom/.syndie/archive/"
AddHandler cgi-script .cgi
AddType application/x-syndie .syndie
</pre>
<p>The CGI accepts posts (uploaded through <a href="manual.html">schedule and put</a>),
writing them to <code>/tmp/cgiImport/</code> (another directory can be chosen
by modifying the <code>import.cgi</code>). In addition, while the CGI allows
anyone to upload posts by default, you can require a password instead - simply
set the <code>$requiredPassphrase</code> in the CGI and share that value with
those authorized to upload posts. Authorized users will then be able to post
by providing that value in the <code>--pass $passphrase</code> parameter for
<code>put</code>).</p>
<p>To fully enable users to upload posts, you will need to set up a
recurring task on your OS running the following Syndie script on occation
(once every hour is perhaps reasonable):</p>
<pre>
login
menu syndicate
bulkimport --dir /tmp/cgiImport --delete true
buildindex
exit
</pre>
<p>This tells Syndie to pull in all of the <code>.syndie</code> files
stored in <code>/tmp/cgiImport</code>, deleting the original files on
completion. The <code>buildindex</code> then regenerates the <code>index-*</code>
files. An example <code>cron</code> line would be: </p><pre>
30 * * * * /home/jrandom/syndie/bin/syndie @/home/jrandom/syndie/bin/bulkimport.syndie > /home/jrandom/syndie/import.log
</pre>
<p>An example upload form:</p>
<pre>
&lt;form action="import.cgi" method="POST" enctype="multipart/form-data"&gt;
Metadata file: &lt;input type="file" name="meta0" /&gt;&lt;br /&gt;
Metadata file: &lt;input type="file" name="meta1" /&gt;&lt;br /&gt;
Metadata file: &lt;input type="file" name="meta2" /&gt;&lt;br /&gt;
Post file: &lt;input type="file" name="post0" /&gt;&lt;br /&gt;
Post file: &lt;input type="file" name="post1" /&gt;&lt;br /&gt;
Post file: &lt;input type="file" name="post2" /&gt;&lt;br /&gt;
&lt;input type="submit" /&gt;
&lt;/form&gt;
</pre>
</div>
</body>
</html>

54
doc/web/db.html Normal file
View File

@ -0,0 +1,54 @@
<html>
<head>
<title>Syndie: database</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<b>[dev]</b>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<div class="leftnav">
<a href="dev.html">[code]</a><br />
<a href="spec.html">[spec]</a><br />
<a href="archive.html">[archive]</a><br />
<b>[database]</b><br />
</div>
<div class="bodycontent">
The database that the <a href="manual.html">text interface</a> uses to store each
nym's keys, organize their preferences, and store decrypted messages is a
<a href="http://java.sun.com/jdbc/">JDBC</a>
accessible <a href="http://www.hsqldb.org/">HSQLDB</a> database. By default, it
loads the database up through HSQLDB's <code>file://</code> schema support, which
allows only one instance at a time to access the database and loads it into memory.
The database can be configured for remote access through HSQLDB's
<code>hsql://hostname:portNum/dbName</code> or
<code>hsqls://hostname:portNum/dbName</code> schema support, offering remote access
(either directly or over SSL/TLS). To use these alternate schemas, simply use the
<a href="manual.html#login">login</a> command with
<code>--db jdbc:hsqldb:hsqls://127.0.0.1:9999/syndie</code> (etc) after starting
up a standalone HSQLDB database configured for remote access.
<p>The database schema itself is kept as part of the Syndie source code as
<code>src/syndie/db/ddl.txt</code>, and is documented therein. Basically, it has
tables to contain individual channels, messages within those channels, the content
stored in those messages (including attachments and references), individual local
nyms, their keys, and their preferences. In addition, it has general configuration
data for managing the database and the associated archive of
<a href="spec.html#message">.syndie</a> messages.</p>
<p>Also of interest are the database schema updates -
<code>src/syndie/db/ddl_update*.txt</code>. They are run sequentially to turn
earlier database schema versions into newer versions of the schema.</p>
</div>
</body>
</html>

80
doc/web/dev.html Normal file
View File

@ -0,0 +1,80 @@
<html>
<head>
<title>Syndie development</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<b>[dev]</b>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<div class="leftnav">
<b>[code]</b><br />
<a href="spec.html">[spec]</a><br />
<a href="archive.html">[archive]</a><br />
<a href="db.html">[database]</a><br />
</div>
<div class="bodycontent">
Releases are versioned as <code>$major</code>.<code>$minor</code><code>$quality</code>,
where <code>$major</code> indicates a substantial functional change,
<code>$minor</code> indicates bugfixes and small improvements, and
<code>$quality</code> values of <code>a,b,rc</code> indicate whether
a release is <b>a</b>lpha quality (substantial changes still in progress,
liable to break, for geeks only), <b>b</b>eta quality (unstable, but
changes are primarily bugfixes, for geeky testers),
<b>r</b>elease <b>c</b>andidate quality (final testing before release),
respectively. Releases without <code>a,b,rc</code> are stable, production quality
releases.</p>
<p>Development discussions for Syndie go on within the
<a href="http://www.i2p.net/">I2P</a> development team, on their
<a href="http://dev.i2p.net/pipermail/i2p/">development list</a>,
<a href="http://forum.i2p.net/forum.php?f=123">forum</a>,
<a href="http://www.i2p.net/meetings">weekly meetings</a>, and
<a href="irc://irc.freenode.net/#i2p">IRC channel</a>.</p>
<p>Installation packages for <a href="download.html">download</a> are
built with various ant targets:<ul>
<li><code>syndie-$version.bin.exe</code><br />
<i>(java installer, includes bin/syndie.exe java launcher)</i><br />
built with the "installer-exe" target, but requires
"-Dlaunch4jdir=/path/to/launch4j" and
"-Dizpackdir=/path/to/izpack_install" (e.g.
<code>ant -Dizpackdir=/home/jrandom/IzPack_install/ -Dlaunch4jdir=/home/jrandom/launch4j-3.0.0-pre1-linux/ installer-exe</code></li>
<li><code>syndie-$version.bin.zip</code><br />
<i>(no java installer, includes bin/syndie.exe java launcher)</i><br />
built with the "java-package-exe" target, but requires
"-Dlaunch4jdir=/path/to/launch4j" (e.g.
<code>ant -Dlaunch4jdir=/home/jrandom/launch4j-3.0.0-pre1-linux/ installer-exe</code></li>
<li><code>syndie-$version.bin-noexe.zip</code><br />
<i>(no java installer, without bin/syndie.exe java launcher)</i><br />
built with the "java-package" target</li>
<li><code>syndie-$version.src.tar.bz2</code><br />
<i>(source package)</i><br />
built with the "source-package" target</li>
</ul></p>
<p>All of the packages can be built into <code>doc/web/dist/</code> with
"<code>ant dist</code>", though you need to include the settings required for
<code>-Dlaunch4jdir</code> and <code>-Dizpackdir</code></p>
<p>With a modern GCC/GCJ (releases prior to 4.0 will fail when they try to
write to the database), you can build native Syndie executables. On *nix/osx,
"<code>make -f Makefile.nix syndie</code>" creates ./syndie, and
"<code>make -f Makefile.nix package</code>" creates a syndie-native.tar.bz2,
which is just like syndie-$version.bin.zip, except bin/syndie is the native
executable instead of a shell script launching java. Work is ongoing for
GCJ/MinGW support, but the Makefile.mingw should work with a viable MinGW
install of GCJ 4.x</p>.
</div>
</body>
</html>

39
doc/web/donate.html Normal file
View File

@ -0,0 +1,39 @@
<html>
<head>
<title>Syndie: donations</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<b>[donate]</b>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
Syndie's development is a volunteer effort, though much of the design
and implementation is done by the entirely-donation-funded fulltime
developer <code>jrandom</code>. Syndie is being built as part of
<a href="http://www.i2p.net/">I2P</a>'s development efforts, and your
generous donations help provide jrandom's very modest cost of living,
as well as development servers and hosting for Syndie and I2P (coming to
approximately <code>$500USD/month</code>).
<p>Even small contributions of $10 or $20 help offset these costs,
and if you can help, please <a href="http://www.i2p.net/donate">do so</a>
(note that the contribution is for Syndie in the memo field).</p>
</div>
</body>
</html>

122
doc/web/download.html Normal file
View File

@ -0,0 +1,122 @@
<html>
<head>
<title>Syndie: download</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<b>[download]</b>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
<!--
Windows binaries:<ul>
<li><a href="http://syndie.i2p.net/dist/syndie-1.000a.win.exe">syndie-1.000a.win.exe</a>
(<a href="http://syndie.i2p.net/dist/syndie-1.000a.win.exe.sig">GPG signature</a>) <b>[note: does not yet exist]</b></li>
<li>Download and run the executable installer.</li>
<li>Prerequisites: None</li>
</ul>
-->
Binaries:<ul>
<li><a href="dist/syndie-1.000a.bin.exe">syndie-1.000a.bin.exe</a>
(<a href="dist/syndie-1.000a.bin.exe.sig">GPG signature</a>) <b>[note: does not yet exist]</b></li>
<li>Download and run the executable installer.<br />
<i>(*nix and OS X users, run the executable by typing
"<code>java -jar syndie-1.000a.bin.exe</code>".
yes, really, you want the .exe)</i></li>
<li>Alternately, <a href="dist/syndie-1.000a.bin.zip">syndie-1.000a.bin.zip</a>
(<a href="dist/syndie-1.000a.bin.zip.sig">GPG signature</a>)
can be used instead - it unzips to ./syndie-1.000a/. This method is
necessary when installing without a graphical interface, but does not
create desktop or start menu shortcuts, and does not include an uninstaller.
</li>
<li>Prerequisites:
<a href="http://java.sun.com/javase/downloads/index.jsp">jre1.4+</a>,
<a href="http://gcc.gnu.org/java/">GCJ/GIJ 4.0+</a>, or
<a href="http://www.kaffe.org/">Kaffe 1.1.7+</a>
(other JVMs may suffice)</li>
</ul>
Source:<ul>
<li><a href="dist/syndie-1.000a.src.tar.bz2">syndie-1.000a.src.tar.bz2</a>
(<a href="dist/syndie-1.000a.src.tar.bz2.sig">GPG signature</a>) <b>[note: does not yet exist]</b></li>
<li>Download and extract the archive, then read the INSTALL file</li>
<li>Prerequisites:
<a href="http://ant.apache.org/">ant 1.6.5</a>, plus
<a href="http://java.sun.com/javase/downloads/index.jsp">jdk1.4+</a>,
<a href="http://gcc.gnu.org/java/">GCJ/GIJ 4.0+</a>, or
<a href="http://www.kaffe.org/">Kaffe 1.1.7+</a>
(other JDKs may suffice)</li>
</ul>
<p>The GPG public key used to sign the distributed files is <code>393F2DF9</code> (fingerprint <code>AE89 D080 0E85 72F0 B777 B2ED C2FA 68C0 393F 2DF9</code>)</p>
Unreleased development source:<ul>
<li><code>mkdir syndie-dev ; cd syndie-dev ; darcs initialize ; darcs pull http://syndie.i2p.net/darcs/</code> (primary sources)</li>
<li>Post new patches to syndie-darcs at i2p dot net: <pre>
darcs record -m "change stuff" -A my@email.addr
darcs send -o myfile.darcs --sign
mail syndie-darcs at i2p dot net &lt; myfile.darcs</pre>
If approved, they will be applied to the <code>http://syndie.i2p.net/darcs/</code>
archive.</li>
<li>Apply patches by pulling from a URL above or from other darcs patches:<pre>
darcs apply --verify=syndie.pubring myfile.darcs</pre>
That applies the patch if it was signed by one of the syndie developers</li>
<li>Read the included INSTALL and related documentation</li>
<li>Prerequisites:
<a href="http://ant.apache.org/">ant 1.6.5</a>, plus
<a href="http://java.sun.com/javase/downloads/index.jsp">jdk1.4+</a>,
<a href="http://gcc.gnu.org/java/">GCJ/GIJ 4.0+</a>, or
<a href="http://www.kaffe.org/">Kaffe 1.1.7+</a>
(other JDKs may suffice)</li>
</ul>
<p>Older releases are <a href="dist/">archived</a></p>
<!--
Gentoo:<ul>
<li><code>emerge syndie</code></li>
Debian:<ul>
<li><code>apt-get syndie</code></li>
-->
<p>To uninstall, if you used the <code>.exe</code> installer, simply
launch the included uninstaller. Otherwise, just remove the directory you
installed Syndie into (<code>$HOME/syndie</code> or <code>C:\syndie</code>).
The Syndie content is stored in <code>$HOME/.syndie</code> by default, so
you should delete that directory as well if you want to remove the
content (and keys).</p>
<p>To upgrade or reinstall, simply install Syndie again on top of itself.
Upgrading or reinstalling does not affect your content or keys, just the
software. To completely wipe any old data, identities, or keys, delete the
<code>$HOME/.syndie</code> directory.</p>
<p>To run multiple separate Syndie instances, you can specify an alternate
data root directory on the Syndie command line
(<code>$HOME/syndie/bin/syndie /another/path</code>). In addition, you can have
many different Syndie nyms within a single Syndie instance (see the "<code>login</code>"
and "<code>register</code>" <a href="manual.html">commands</a>).</p>
<p>These packages include the java 1.4 compiled HSQLDB 1.8.0.5 - you can
replace the included lib/hsqldb.jar with newer versions, or remove it and
adjust the <code>bin/syndie</code> script to reference the local hsqldb.jar.</p>
</div>
</body>
</html>

109
doc/web/faq.html Normal file
View File

@ -0,0 +1,109 @@
<html>
<head>
<title>Syndie: FAQ</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<b>[faq]</b>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
<ul id="menu">
<li><a href="#why">Why does Syndie matter?</a></li>
<li><a href="#whohostsarchives">Who will host Syndie archives?</a></li>
<li><a href="#license">What license is Syndie released under</a></li>
<li><a href="#relationship">What is the relationship between Syndie and $x?</a></li>
</ul>
<b id="why">Why does Syndie matter?</b> (<a href="#menu">up</a>)
<p>What do forum/blogging tools have to do with providing strong anonymity?</p>
<p>The answer: <b>*everything*</b>.</p>
<p>To briefly summarize:<ul>
<li>Syndie's design as an anonymity-sensitive client application carefully
avoids the intricate data sensitivity problems that nearly every
application not built with anonymity in mind does not.</li>
<li>By operating on the content layer, Syndie does not depend upon the
performance or reliability of distributed networks like I2P, Tor, or
Freenet, though it can exploit them where appropriate.</li>
<li>By doing so, it can operate fully with small, ad-hoc mechanisms for
content distribution - mechanisms which may not be worth the effort
for powerful adversaries to counteract (since the 'payoff' of busting
just a few dozen people will likely exceed the cost of mounting the
attacks)</li>
<li>This implies that Syndie will be useful even without a few million
people using it - small unrelated groups of people should set up their
own private Syndie distribution scheme without requiring any
interaction with or even awareness by any other groups.</li>
<li>Since Syndie does not rely upon real-time interaction, it can even
make use of high latency anonymity systems and techniques to avoid the
attacks that all low latency systems are vulnerable to (such as
passive intersection attacks, passive and active timing attacks, and
active blending attacks).</li>
</ul>
<p>Alternately, you can review some of Syndie's
<a href="usecases.html">use cases</a>.</p>
<b id="whohostsarchives">Who will host Syndie archives?</b> (<a href="#menu">up</a>)
<p><i>(irc log edited for clarity)</i></p>
<ul>
<li>&lt;bar&gt; a question i've been pondering is, who is later going to have balls
big enough to host syndie production servers/archives?</li>
<li>&lt;bar&gt; aren't those going to be as easy to track down as the eepsites are
today?</li>
<li>&lt;jrandom&gt; public syndie archives do not have the ability to *read*
the content posted to forums, unless the forums publish the keys to do so</li>
<li>&lt;jrandom&gt; and see the second paragraph of
<a href="usecases.html#decentralizedforum">usecases.html</a></li>
<li>&lt;jrandom&gt; of course, those hosting archives given lawful orders to
drop a forum will probably do so</li>
<li>&lt;jrandom&gt; (but then people can move to another archive, without disrupting
the forum's operation)</li>
<li>&lt;void&gt; yeah, you should mention the fact that migration to a
different medium is going to be seamless</li>
<li>&lt;bar&gt; if my archive shuts down, i can upload my whole forum to a new one,
right?</li>
<li>&lt;jrandom&gt; 'zactly bar</li>
<li>&lt;void&gt; they can use two methods at the same time while migrating</li>
<li>&lt;void&gt; and anyone is able to synchronize the mediums</li>
<li>&lt;jrandom&gt; right void</li>
</ul>
<b id="license">What license is Syndie released under?</b> (<a href="#menu">up</a>)
<p>Short answer: its probably simplest to consider Syndie to be released under a
BSD-like license.</p>
<p>Medium answer: nearly all of the code is released into the public domain, with
some files under MIT or BSD licenses. Syndie is also linked against a library
released under the GPL with the linking exception (which means Syndie does <b>not</b>
have to be GPLed)</p>
<p>Long answer: read the LICENSE file in the package.</p>
<b id="relationship">What is the relationship between Syndie and $x?</b> (<a href="#menu">up</a>)
<p>The <a href="related.html">relationship</a> between Syndie and other
efforts have been moved to their own page.</p>
</div>
</body>
</html>

132
doc/web/features.html Normal file
View File

@ -0,0 +1,132 @@
<html>
<head>
<title>Syndie: features</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<b>[features]</b>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav" id="menu">
</div>
-->
<div class="bodycontent">
<p>While its structure leads to a large number of
different configurations, most needs will be met by selecting one of
the options from each of the following three criteria:</p><ul>
<li>Forum types:<ul>
<li>Single author (typical blog)</li>
<li>Multiple authors (multiauthor blog)<a href="#starstar">**</a></li>
<li>Open (newsgroups, though restrictions may be included so that only
authorized<a href="#starstar">**</a> users can post new threads, while anyone can comment on
those new threads)</li>
</ul></li>
<li>Visibility:<ul>
<li>Anyone can read anything</li>
<li>Only authorized<a href="#star">*</a> people can read posts, but some metadata is exposed</li>
<li>Only authorized<a href="#star">*</a> people can read posts, or even know who is posting</li>
<li>Only authorized<a href="#star">*</a> people can read posts, and no one knows who is posting</li>
</ul></li>
<li>Comments/replies:<ul>
<li>Anyone can comment or send private replies to the author/forum owner</li>
<li>Only authorized<a href="#starstar">**</a> people can comment, and anyone can send private replies</li>
<li>No one can comment, but anyone can send private replies</li>
<li>No one can comment, and no one can send private replies</li>
</ul></li>
</ul>
<p style="text: small"><b id="star">*</b>
reading is authorized by giving people the symmetric key or passphrase
to decrypt the post. Alternately, the post may include a publicly
visible prompt, where the correct answer serves to generate the
correct decryption key.</p>
<p style="text: small"><b id="starstar">**</b>
posting, updating, and/or commenting is authorized by providing those
users with asymmetric private keys to sign the posts with, where the
corresponding public key is included in the forum's metadata as
authorized to post, manage, or comment on the forum. Alternately, the
signing public keys of individual authorized users may be listed in
the medtata.</p>
<p>Individual posts may contain many different elements:</p><ul>
<li>Any number of pages, with out of band data for each page specifying
the content type, language, etc. Any formatting may be used, as its
up to the client application to render the content safely - plain text
must be supported, and clients that can should support HTML.</li>
<li>Any number of attachments (again, with out of band data describing the
attachment)</li>
<li>A small avatar for the post (but if not specified, the author's
default avatar is used)</li>
<li>A set of references to other posts, forums, archives, URLs, etc (which
may include the keys necessary to post, manage, or read the referenced
forums)</li>
</ul>
<p>On the whole, Syndie works at the *content layer* - individual posts are
contained in encrypted zip files, and participating in the forum means
simply sharing these files. There are no dependencies upon how the files
are transferred (over <a href="http://www.i2p.net/">I2P</a>,
<a href="http://tor.eff.org/">Tor</a>,
<a href="http://www.freenetproject.org/">Freenet</a>,
<a href="http://www.gnutella.org/">gnutella</a>,
<a href="http://www.bittorrent.com/">bittorrent</a>,
<a href="http://en.wikipedia.com/wiki/RSS">RSS</a>,
<a href="http://en.wikipedia.com/wiki/Usenet">usenet</a>,
<a href="http://en.wikipedia.com/wiki/Email">email</a>),
but simple aggregation and distribution tools will be
bundled with the standard Syndie release.</p>
<p>Interaction with the Syndie content will occur in several ways. First,
there is a scriptable text based interface, allowing basic command line
and interactive reading from, writing to, managing, and synchronizing
the forums. For instance, the following is a simple script to generate
a new "message of the day" post -</p>
<pre>
login
menu post
create --channel 0000000000000000000000000000000000000000
addpage --in /etc/motd --content-type text/plain
addattachment --in ~/webcam.png --content-type image/png
listauthkeys --authorizedOnly true
authenticate 0
authorize 0
set --subject "Today's MOTD"
set --publicTags motd
execute
exit
</pre>
<p>Simply pipe that through the syndie executable and the deed is done:</p>
<pre> cat motd-script | ./syndie > syndie.log</pre>
<p>Additionally, there is work going on for a graphical Syndie interface,
which includes the safe rendering of plain text and HTML pages (of
course, with support for transparent integration with Syndie's
features).</p>
<p>Applications based on the old Syndie's "sucker" code will enable the
scraping and rewriting of normal web pages and web sites so that they
can be used as single or multipage Syndie posts, including images and
other resources as attachments.</p>
<p>Down the line, firefox/mozilla plugins are planned to both detect and
import Syndie formatted files and Syndie references, as well as notify
the local Syndie GUI that a particular forum, topic, tag, author, or
search result should be brought into focus.</p>
<p>Of course, since Syndie is, at its core, a content layer with a defined
file format and cryptographic algorithms, other applications or
alternate implementations will probably be brought forward over time.</p>
</div>
</body>
</html>

34
doc/web/index.html Normal file
View File

@ -0,0 +1,34 @@
<html>
<head>
<title>Syndie</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<b>[home]</b>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav" id="menu">
</div>
-->
<div class="bodycontent">
<span id="what">Syndie</span> is an open source system for operating
distributed forums (<i><a href="usecases.html">Why would you use Syndie?</a></i>),
offering a secure and consistent interface to various anonymous and non-anonymous
content networks.
<p><div align="center"><img src="syndie_nets.png" alt="Syndie can work with anonymizer, circumventor, JAP, I2P, Tor, mixminion, mixmaster, Freenet, gnutella, OpenDHT" /></div></p>
</div>
</body>
</html>

445
doc/web/manual.html Normal file
View File

@ -0,0 +1,445 @@
<html>
<head>
<title>Syndie: manual</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<b>[manual]</b>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
<p>The <a href="index.html">Syndie</a> text interface is a context-sensitive
menu driven application, and is fed commands from the standard input,
allowing scriptable operation.</p>
<p>The application itself can be launched with zero, one, or two parameters:</p>
<pre> <code>syndie [@script] [data_root]</code></pre>
<p>The optional <code>@script</code> parameter reads in the contents of the
<code>script</code> file, running them as if they came from the standard input.
The optional <code>data_root</code> parameter tells Syndie where to locate the
database, archive, and related data files. If not specified, it uses
<code>$HOME/.syndie/</code> (or <code>%HOME%\.syndie</code> on windows).</p>
<p>The menus are outlined below, with unimplemented commands prefixed by
<code>//</code>.</p>
<ul id="menu">
<li><a href="#start">Start menu</a></li>
<li><a href="#general">General commands</a></li>
<li><a href="#read">Read menu</a></li>
<li><a href="#manage">Manage menu</a></li>
<li><a href="#post">Post menu</a></li>
<li><a href="#syndicate">Syndicate menu</a></li>
</ul>
<hr />
<ul>
<li id="start">Start menu (not logged in) (<a href="#menu">up</a>)<dl>
<dt id="start_login"><code>login [--db $jdbcURL] [--login $loginName --pass $password]</code></dt>
<dd>logs into Syndie at the default database location using the default login and
password (each Syndie login has its own set of keys - just because one login
has been authorized to post to a channel doesn't mean other logins are).</dd>
<dt id="start_register"><code>register [--db $jdbcURL] --login $loginName --pass $password --name $name</code></dt>
<dd>registers a new login in the Syndie database</dd>
<dt id="start_restore"><code>restore --in $file [--db $jdbcURL]</code></dt>
<dd>Restore the Syndie database (backed up with the
<a href="#general_backup">backup</a> command), and archive, if
included).</dd>
</dl></li>
<li id="general">General commands (available across all logged in menus) (<a href="#menu">up</a>)<dl>
<dt id="general_logout"><code>logout</code></dt>
<dd>Disconnect from the database, but do not exit Syndie</dd>
<dt id="general_menu"><code>menu [$menuName]</code></dt>
<dd>Switch to the given menu, or list available menus</dd>
<dt id="general_exit"><code>exit</code></dt>
<dd>Logout and exit Syndie</dd>
<dt id="general_up"><code>up</code></dt>
<dd>Move up a menu</dd>
<dt id="general_togglePaginate"><code>togglePaginate</code></dt>
<dd>Start/stop paginating the output every 10 lines</dd>
<dt id="general_toggleDebug"><code>toggleDebug</code></dt>
<dd>Start/stop displaying verbose messages to the user</dd>
<dt id="general_prefs"><code>prefs [--debug $boolean] [--paginate $boolean] [--httpproxyhost $hostname --httpproxyport $portNum] [--archive $archiveURL]</code></dt>
<dd>Update the current nym's preferences, specifying the debug and
paginate toggle state, the default HTTP proxy used for HTTP syndication,
and the default archive location. These preferences are loaded whenever
the nym logs in. If <code>prefs</code> is called with no arguments,
then the preferences are simply displayed and not updated.</dd>
<dt id="general_import"><code>import --in $filename</code></dt>
<dd>Import the specified <code>.syndie</code> file (either a metadata message or
a post). Alternately, it can import key files generated by
<code>keygen</code>.</dd>
<dt id="general_keygen"><code>keygen --type (read|manage|post|reply) [--scope $channelHash] (--pubOut $publicKeyFile --privOut $privateKeyFile | --sessionOut $sessionKeyFile)</code></dt>
<dd>Generate a new crypto key for reading posts (an AES256 session key),
signing channel management messages (a DSA1024 public and private keypair),
signing channel posts (a DSA1024 public and private keypair),
or reading/writing channel reply messages (an Elgamal2048 public and private
keypair). The <code>--scope</code> parameter is just an informational field
included in the key files so that on <a href="#general_import">import</a>, they
can be used appropriately.</dd>
<dt id="general_version"><code>version</code></dt>
<dd>Display the current version of Syndie</dd>
<dt id="general_help"><code>?</code></dt>
<dt id="general_help"><code>help</code></dt>
<dd>List commands available within the current menu</dd>
<dt id="general_sql"><code>sql $sqlQuery</code> <i>(advanced)</i></dt>
<dd>Execute the given SQL query against the database, displaying the results</dd>
<dt id="general_init"><code>init $jdbcURL</code> <i>(advanced)</i></dt>
<dd>Create a new Syndie database at the given URL</dd>
<dt id="general_backup"><code>backup --out $file [--includeArchive $boolean]</code></dt>
<dd>Backup the Syndie database to the given (compressed) file, optionally
including the full content of the archive. If the filename specified
includes the string "DATE", those four characters are replaced with the
current date (YYYY-MM-DD)</dd>
<dt id="general_builduri"><code>builduri --url http://foo/bar</code></dt>
<dt id="general_builduri"><code>builduri --channel $chanHash [--message $messageId [--page $pageNum]]</code></dt>
<dt id="general_builduri"><code>builduri --archive $url [--password $pass]</code></dt>
<dd>Helpers for building Syndie URIs</dd>
<dt id="general_history"><code>history</code></dt>
<dd>Display the command history</dd>
<dt id="general_!!"><code>!!</code></dt>
<dd>Execute the previous command again</dd>
<dt id="general_!num"><code>!$num</code></dt>
<dd>Execute the $num-th command in the history</dd>
<dt id="general_!-num"><code>!-$num</code></dt>
<dd>Execute the command $num lines ago</dd>
<dt id="general_^a"><code>^a[^b]</code></dt>
<dd>Replace the first occurrence of <code>a</code> with <code>b</code> in
the previous command, and run it. If <code>^b</code> is not specified,
the first occurrence of <code>a</code> is removed.</dd>
<dt id="general_alias"><code>alias [foo $bar]</code></dt>
<dd>Configure the Syndie interface to interpret the command "$foo" as the command
line $bar. $bar can contain any number of options, and can use ";" as a command
delimiter, allowing e.g.
<code>"alias bugs menu read; threads --channel all --tags syndie,bug,-wontfix,-closed,-worksforme,-claim"</code>.
Aliases work in all menu contexts, and are run after attempting to interpret
the command as a normal instruction - meaning you cannot effectively override
existing commands with aliases.</dd>
</dl></li>
<li id="read">Read menu (<a href="#menu">up</a>)<dl>
<dt id="read_channels"><code>channels [--unreadOnly $boolean] [--name $name] [--hash $hashPrefix]</code></dt>
<dd>Display a list of channels the user can access that matches the given criteria.
The --name and --hash options limit the scope to channels whose name or identity hash
starts with the given value.</dd>
<dt id="read_next"><code>next [--lines $num]</code></dt>
<dt id="read_prev"><code>prev [--lines $num]</code></dt>
<dd>Paginate through the result set of channels or messages, 10 (or $num) at a time</dd>
<dt id="read_meta"><code>meta [--channel ($index|$hash)]</code></dt>
<dd>Display the current channel's metadata. If $index is specified, it refers to the $index-th
channel in the 'channels' output, or if $hash is specified, it refers to the channel whose
identity hash is given.</dd>
<dt id="read_messages"><code>messages --channel ($index|$hash) [--includeUnauthorized $boolean] [--includeUnauthenticated $boolean]</code><dt>
<dd>Display a list of messages matching the given criteria.</dd>
<dt id="read_threads"><code>threads [--channel ($index|$hash|all)] [--tags [-]tag[,[-]tag]*] [--includeUnauthorized $boolean] [--compact $boolean]</code><dt>
<dd>Display a list of threads matching the given criteria. The tags parameter
picks threads where at least one message has each of the tags, and that none of
the messages have any of the tags prefaced by <code>-</code>. The
display can be fairly verbose or it can be compact (limiting the output
to one line per thread). If called with no arguments, then it just
displays the last set of matching threads again.</dd>
<dt id="read_view"><code>view (--message ($index|$uri)|--thread $index) [--page $n]</code></dt>
<dd>Display the first page of the specified message, including relevent metadata and the
message's position within the thread, as well as references to other resources</dd>
<dt id="read_threadnext"><code>threadnext [--position $position]</code></dt>
<dd>Jump to the next message in the thread (or to the one at the position specified)</dd>
<dt id="read_threadprev"><code>threadprev [--position $position]</code></dt>
<dd>Jump to the previous message in the thread (or to the one at the position specified)</dd>
<dt id="read_importkey"><code>importkey --position $position</code></dt>
<dd>Import the referenced private key to post or manage a channel, or the session key
to read posts for a channel.</dd>
<dt id="read_export"><code>export [--message ($index|$uri)] --out $directory</code></dt>
<dd>Dumps the full set of pages, attachments, references, metadata, and a status.txt
summarizing the message to the given directory.</dd>
<dt id="read_save"><code>save [--message ($index|$uri)] (--page $n|--attachment $n) --out $filename</code></dt>
<dd>Save just the given page or attachment to the given file</dd>
<dt id="read_reply"><code>reply</code></dt>
<dd>Jumps to the post menu, prepopulating the channel and references fields</dd>
<dt id="read_ban"><code>ban [--scope (author|channel|$hash)] [--delete $boolean]</code></dt>
<dd>ban the author or channel so that no more posts from that author
or messages by any author in that channel will be allowed into the
Syndie archive. If --delete is specified, the messages themselves
will be removed from the archive as well as the database</dd>
<dt id="read_decrypt"><code>decrypt [(--message $msgId|--channel $channelId)] [--passphrase pass]</code></dt>
<dd>If the message or channel metadata in question was imported into the
database before, but could not be decrypted at the time, you can attempt
to decrypt it again later, using either the provided passphrase or the
currently logged in nym's set of reply and channel read keys.</dd>
<dt id="read_watch">//<code>watch (author|channel) [--nickname $name] [--category $nameInTree]</code></dt>
</dl></li>
<li id="manage">Manage menu (<a href="#menu">up</a>)<dl>
<dt id="manage_channels"><code>channels</code></dt>
<dd>Display a list of channels that the current nym can manage</dd>
<dt id="manage_next"><code>next [--lines $num]</code></dt>
<dt id="manage_prev"><code>prev [--lines $num]</code></dt>
<dd>Paginate through the result set of channels, 10 (or $num) at a time</dd>
<dt id="manage_meta"><code>meta [--channel ($index|$hash)]</code></dt>
<dd>Display the current channel's metadata. If $index is specified, it refers to the $index-th
channel in the 'channels' output, or if $hash is specified, it refers to the channel whose
identity hash is given.</dd>
<dt id="manage_create"><code>create</code><dt>
<dt id="manage_update"><code>update (--channel $index|$hash)</code></dt>
<dd>Begin the process of creating a new channel or updating an existing channel</dd>
<dt id="manage_set"><code>set [$option=$value]*</code></dt>
<dd>Set various options on the channel being created or updated. They can be specified
individually or on a single line.</dd>
<dt id="manage_setname"><code>set --name $channelName</code></dt>
<dd>Sets the channel's suggested name</dd>
<dt id="manage_setdescription"><code>set --description $desc</code></dt>
<dd>Sets the channel's description</dd>
<dt id="manage_setavatar"><code>set --avatar $filename</code></dt>
<dd>Bundle the 32x32 pixel PNG image as the channel's avatar</dd>
<dt id="manage_setedition"><code>set --edition $editionNum</code></dt>
<dd>Specify the edition number to use for this metadata (if not specified,
a randomized date-based edition will be used. note that the edition must
be higher than the previous edition number)</dd>
<dt id="manage_setexpiration"><code>set --expiration $yyyyMMdd</code></dt>
<dd>Suggest a date after which the channel metadata and all associated posts
should be dropped (if not updated by then)</dd>
<dt id="manage_setpublicposting"><code>set --publicPosting $boolean</code></dt>
<dd>If true, anyone is implicitly authorized to post to the channel. If false,
only those listed may.</dd>
<dt id="manage_setpublicreplies"><code>set --publicReplies $boolean</code></dt>
<dd>If true, anyone is implicitly authorized to post replies to authorized
messages in the channel, but not to post up new discussion threads. If
false, only those listed may. This value is ignored if
<a href="#manage_setpublicposting">publicPosting</a> is set to true.</dd>
<dt id="manage_setpubtag"><code>set --pubTag [$tag[,$tag]*]</code></dt>
<dd>Specify tags that unauthorized people will be able to see regarding
the channel</dd>
<dt id="manage_setprivtag"><code>set --privTag [$tag[,$tag]*]</code></dt>
<dd>Specify additional tags that only authorized people will be able to see
regarding the channel</dd>
<dt id="manage_setrefs"><code>set --refs $filename</code></dt>
<dd>Bundle the references loaded from the given file with the channel.
The format is simple: <code>[[\t]*$name\t$uri\t$refType\t$description\n]*</code>.
The tab indentation at the beginning of the line determines the tree structure,
and blank values are allowed for various fields. </dd>
<dt id="manage_setpubarchive"><code>set --pubArchive [$syndieURI[,$syndieURI]*]</code></dt>
<dd>Specify a list of Syndie archives that people can look to for updates
regarding this channel. The URIs themselves may include passphrases
necessary for posting, etc.</dd>
<dt id="manage_setprivarchive"><code>set --privArchive [$syndieURI[,$syndieURI]*]</code></dt>
<dd>Specify an additional list of Syndie archives that only authorized people
will be able to see regarding the channel.</dd>
<dt id="manage_setencryptcontent"><code>set --encryptContent $boolean</code></dt>
<dd>Specify whether the metadata should encrypt its body without publicizing
the body's encryption key. If it does, a private channel read key is included
in the metadata that can be used to view or encrypt posts in the channel.</dd>
<dt id="manage_setbodypassphrase"><code>set --bodyPassphrase $passphrase</code></dt>
<dd>Encrypt the channel metadata with a key derived from the given passphrase</dd>
<dt id="manage_setbodypassphraseprompt"><code>set --bodyPassphrasePrompt $prompt</code></dt>
<dd>When encrypting the channel metadata with a passphrase based key, include the
given prompt in the clear, suggesting the passphrase to authorized readers.
e.g. <code>set --bodyPassphrasePrompt "1+1" --bodyPassphrase "2"</code></dd>
<dt id="manage_listnyms"><code>listnyms [--name $namePrefix] [--channel $hashPrefix]</code></dt>
<dd>Display an indexed list of channel signing keys matching the given
criteria, allowing simple indexed selection of authorized nyms with
<a href="#manage_addnym">addnym</a></dd>
<dt id="manage_addnym"><code>addnym (--nym $index|--key $base64(pubKey)) --action (manage|post)</code></dt>
<dd>Add the given key to the list of authorized channel managers or posters</dd>
<dt id="manage_removenym"><code>removenym (--nym $index|--key $base64(pubKey)) --action (manage|post)</code></dt>
<dd>Remove the given key from the list of authorized channel managers or posters</dd>
<dt id="manage_preview"><code>preview</code></dt>
<dd>Summarize the channel configuration before committing it</dd>
<dt id="manage_execute"><code>execute</code></dt>
<dd>Create a signed channel metadata file describing the new or updated channel
configuration, importing the channel into the current Syndie database,
and importing the channel manage, reply decryption, and read keys into the
currently logged in Syndie account. This also clears the current create or
update state</dd>
<dt id="manage_cancel"><code>cancel</code></dt>
<dd>Cancel the current create or update state</dd>
</dl></li>
<li id="post">Post menu (<a href="#menu">up</a>)<dl>
<dt id="post_channels"><code>channels [--capability (manage|post)] [--name $name] [--hash $prefix]</code></dt>
<dd>Display a list of channels matching the given criteria.</dd>
<dt id="post_next"><code>next [--lines $num]</code></dt>
<dt id="post_prev"><code>prev [--lines $num]</code></dt>
<dd>Paginate through the result set of channels, 10 (or $num) at a time</dd>
<dt id="post_meta"><code>meta [--channel ($index|$hash)]</code></dt>
<dd>Display the current channel's metadata. If $index is specified, it refers to the $index-th
channel in the 'channels' output, or if $hash is specified, it refers to the channel whose
identity hash is given.</dd>
<dt id="post_create"><code>create --channel ($index|$hash)</code></dt>
<dd>Begin the process of creating a new post</dd>
<dt id="post_addpage"><code>addpage [--page $num] --in ($filename|stdin) [--type $contentType]</code></dt>
<dd>Add a new page to the post, pulling data from the given file. If "stdin"
is the <code>--in</code> parameter, the content is read from the standard
input until terminated with a line containing only a single ".". The
newlines are stripped on each line so that it ends with "\n" for all
users, regardless of whether their OS uses "\n", "\r\n", or "\r" for line
terminators.</dd>
<dt id="post_listpages"><code>listpages</code></dt>
<dd>Display a list of pages already scheduled for inclusion in the post</dd>
<dt id="post_delpage"><code>delpage $index</code></dt>
<dd>Remove the $index-th page</dd>
<dt id="post_addattachment"><code>addattachment [--attachment $num] --in $filename [--type $contentType] [--name $name] [--description $desc]</code></dt>
<dd>Add a new attachment to the post</dd>
<dt id="post_listattachments"><code>listattachments</code></dt>
<dd>Display a list of attachments already scheduled for inclusion in the post</dd>
<dt id="post_delattachment"><code>delattachment $index</code></dt>
<dd>Remove the $index-th attachment</dd>
<dt id="post_listkeys"><code>listkeys [--scope $scope] [--type $type]</code></dt>
<dd>Display the hash of private keys that the current Syndie nym has access to that
match the given criteria, so that they can be fed into <code>addref</code></dd>
<dt id="post_addref"><code>addref --in $filename</code></dt>
<dd>Import all of the references in the given file</dd>
<dt id="post_addref2"><code>addref [--name $name] --uri $uri [--reftype $type] [--description $desc]</code></dt>
<dd>Add the specified reference to the post</dt>
<dt id="post_addref3"><code>addref --readkey $keyHash --scope $scope [--name $name] [--description $desc]</code></dt>
<dd>Add a reference that includes the given channel read key (AES256)</dd>
<dt id="post_addref4"><code>addref --postkey $keyHash --scope $scope [--name $name] [--description $desc]</code></dt>
<dd>Add a reference that includes the given channel post key (DSA private)</dd>
<dt id="post_addref5"><code>addref --managekey $keyHash --scope $scope [--name $name] [--description $desc]</code></dt>
<dd>Add a reference that includes the given channel management key (DSA private)</dd>
<dt id="post_addref6"><code>addref --replykey $keyHash --scope $scope [--name $name] [--description $desc]</code></dt>
<dd>Add a reference that includes the given channel's reply key (ElGamal private)</dd>
<dt id="post_listrefs"><code>listrefs</code></dt>
<dd>Display a list of references already added, prefixed by an index</dd>
<dt id="post_delref"><code>delref $index</code></dt>
<dd>Delete the given reference from the post</dd>
<dt id="post_addparent"><code>addparent --uri $uri</code></dt>
<dd>Add the given post's URI as a parent to the new post</dd>
<dt id="post_listparents"><code>listparents</code></dt>
<dd>Display a list of URIs this new post will be marked as replying to, with the
most recent parent first</dd>
<dt id="post_delparent"><code>delparent $index</code></dt>
<dd>Remove the $index-th parent</dd>
<dt id="post_listauthkeys"><code>listauthkeys [--authorizedonly $boolean]</code></dt>
<dd>Display an indexed list of signing keys that the current Syndie login has access
to. If requested, this only includes those keys which have been marked as
authorized to post in (or manage) the current channel.</dd>
<dt id="post_authenticate"><code>authenticate $index</code></dt>
<dd>Use the specified key to authenticate the post as coming from the given author</dd>
<dt id="post_authorize"><code>authorize $index</code></dt>
<dd>Use the specified key to authorize the post as coming from someone allowed to
post in the channel</dd>
<dt id="post_listreadkeys"><code>listreadkeys</code></dt>
<dd>Display an indexed list of known channel read keys that we can use to encrypt
the message</dd>
<dt id="post_setreadkey"><code>set --readkey (public|$index|pbe --passphrase $pass --prompt $prompt)</code></dt>
<dd>If <code>public</code>, create a random key and publicize it in the post's
publicly readable headers. if pbe, then derive a read key from the
passphrase, publicizing the prompt in the public headers. Otherwise,
use the indexed channel read key</dd>
<dt id="post_setcancel"><code>//set --cancel $uri[,$uri]*</code></dt>
<dd>Include instructions to cancel the given posts</dd>
<dt id="post_setmessageid"><code>set --messageId ($id|date)</code></dt>
<dd>Specify the post's messageId, or a random ID based on the current date</dd>
<dt id="post_setsubject"><code>set --subject $subject</code></dt>
<dd>Specify the post's subject</dd>
<dt id="post_setavatar"><code>set --avatar $filename</code></dt>
<dd>Specify the (32x32 pixel PNG) avatar to bundle with the post</dd>
<dt id="post_setencrypttoreply"><code>set --encryptToReply $boolean</code></dt>
<dd>Encrypt the post to the channel's ElGamal reply key so only the channel owner
can read the reply.</dd>
<dt id="post_setoverwrite"><code>set --overwrite $uri</code></dt>
<dd>State that the new post will be trying to overwrite the given post, though
the overwriting may not necessarily be honored</dd>
<dt id="post_setexpiration"><code>set --expiration ($yyyyMMdd|none)</code></dt>
<dd>Suggest that the post be deleted after the given date (or never)</dd>
<dt id="post_setforcenewthread"><code>set --forceNewThread $boolean</code></dt>
<dd>State that even if the post is replying to existing messages, it should
branch off into a new thread</dd>
<dt id="post_setrefusereplies"><code>set --refuseReplies $boolean</code></dt>
<dd>State that only the author (and channel owner) can reply to the post within
the current thread</dd>
<dt id="post_setpublictags"><code>set --publicTags $tag[,$tag]*</code></dt>
<dd>Specify tags that unauthorized people will be able to see regarding the message</dd>
<dt id="post_setprivatetags"><code>set --privateTags $tag[,$tag]*</code></dt>
<dd>Specify tags that only people authorized to read the post can see</dd>
<dt id="post_preview"><code>preview [--page $n]</code></dt>
<dd>View the post as it will appear</dd>
<dt id="post_execute"><code>execute</code></dt>
<dd>Actually generate the post's signed .syndie file, and import it into the local
Syndie database, clearing the creation state</dd>
<dt id="post_cancel"><code>cancel</code></dt>
<dd>Cancel the current creation state</dd>
</dl></li>
<li id="syndicate">Syndicate menu (<a href="#menu">up</a>)<dl>
<dt id="syndicate_buildindex"><code>buildindex</code></dt>
<dd>creates or updates the index of the local archive</dd>
<dt id="syndicate_getindex"><code>getindex --archive $url [--proxyHost $host --proxyPort $port] [--pass $pass]
[--scope (all|new|meta|unauth)] [--channel $chan]</code></dt>
<dd>fetch the index of the referenced archive, pulling in their list of
all posts, new posts, only metadata, or new unauthenticated posts.</dd>
<dt id="syndicate_diff"><code>diff</code></dt>
<dd>summarize the differences between the fetched index and the local db</dd>
<dt id="syndicate_fetch"><code>fetch [--style (diff|known|metaonly|pir|unauth)] [--includeReplies $boolean]</code></dt>
<dd>actually fetch the posts/replies/metadata</dd>
<dt id="syndicate_nextpbe"><code>(nextpbe|prevpbe) [--lines $num]</code></dt>
<dd>paginate through the messages using passphrase based encryption</dd>
<dt id="syndicate_resolvepbe"><code>resolvepbe --index $num --passphrase $passphrase</code></dt>
<dd>import the indexed message by using the specified passphrase</dd>
<dt id="syndicate_schedule"><code>schedule --put (outbound|outboundmeta|archive|archivemeta)
[--deleteOutbound $boolean] [--knownChanOnly $boolean]</code></dt>
<dd>schedule a set of messages to be posted. if specified, after a
successful post transmission, the uploaded outbound messages will be
deleted (but not messages stored in the archive only)</dd>
<dt id="syndicate_post"><code>post [--postURL $url] [--passphrase $pass]</code></dt>
<dd>post the scheduled messages to the current archive (optionally using
a different URL). This works by sending an RFC1867 HTTP POST
containing the contents of the .syndie files in the fields <code>meta$n</code>
and <code>post$n</code>, where n >= 0. If the passphrase is specified, it
includes <code>pass=$pass</code>. Perhaps later that will switch to
base64(HMAC($YYYYMMDD,PBE($password))) so it can be slightly secure even
in absence of TLS/etc?</dd>
<dt id="syndicate_bulkimport"><code>bulkimport --dir $directory --delete $boolean</code></dt>
<dd>import all of the .syndie files in the given directory, deleting them on completion</dd>
<dt id="syndicate_listban"><code>listban</code></dt>
<dd>list the channels currently banned in the local archive</dd>
<dt id="syndicate_unban"><code>unban [--scope $index|$chanHash]</code></dt>
<dd>remove the specified ban</dd>
</dl></li>
</ul>
An example script:
<pre> login
menu post
create --channel 0000000000000000000000000000000000000000
addpage --in /etc/motd --content-type text/plain
addattachment --in ~/public_html/webcam.png --content-type image/png --name cam.png
listauthkeys --authorizedOnly true
authenticate 0
authorize 0
set --subject "Today's MOTD"
set --publicTags motd
execute</pre>
<!--
//login > watch >
status
walk through the categorized tree of channels watched by the
nym, displaying their status (# unread/read, last activity
date, etc), along side an index
read $index
jumps over to the read menu and runs:
messages $hash --includeUnauthorized false --includeUnauthenticated false
unwatch $index
rename $index $newName
move $index $newCategoryIndex
newcategory $name $parentIndex
watch $channel $name $parentIndex
-->
</div>
</body>
</html>

77
doc/web/related.html Normal file
View File

@ -0,0 +1,77 @@
<html>
<head>
<title>Syndie: Related efforts</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
<h3>Relationship between Syndie and:</h3>
<ul id="menu">
<li><a href="#i2p">I2P</a></li>
<li><a href="#tor">Tor</a></li>
<li><a href="#freenet">Freenet</a></li>
<li><a href="#usenet">Usenet</a></li>
<li><a href="#opendht">OpenDHT</a></li>
<li><a href="#feedspace">Feedspace</a></li>
<li><a href="#feedtree">Feedtree</a></li>
<li><a href="#eternity">Eternity Service</a></li>
<li><a href="#pgp">PGP/GPG</a></li>
</ul>
<b id="i2p">I2P</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="Tor">Tor</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="freenet">Freenet</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="usenet">Usenet</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="opendht">OpenDHT</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="feedspace">Feedspace</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="feedtree">Feedtree</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="eternity">Eternity Service</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
<b id="pgp">PGP/GPG</b> (<a href="#menu">up</a>)
<p><!-- info here --></p>
</div>
</body>
</html>

66
doc/web/roadmap.html Normal file
View File

@ -0,0 +1,66 @@
<html>
<head>
<title>Syndie: roadmap</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<b>[roadmap]</b>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
The first public <a href="index.html">Syndie</a> release includes the following
baseline functionality:
<ul>
<li>The Syndie (scriptable) <a href="manual.html">text interface</a>, packaged
up as a typical java application, or buildable with modern GCJs.</li>
<li>Support for all types of Syndie forums</li>
<li>Manual Syndie distribution (as <code>.syndie</code> files), and HTTP
syndication to public <a href="archive.html">Syndie archives</a> through
the (scriptable) text interface.</li>
<li>Specs for the <a href="spec.html#message"><code>.syndie</code> file format</a>
and encryption algorithms, <a href="spec.html#uri">Syndie URIs</a>, and the
<a href="db.html">database schema</a>, allowing extensions and alternate
implementations by third parties</li>
</ul>
Subsequent releases will improve Syndie's capabilities across several dimensions:
<ul>
<li>User interface:<ul>
<li>SWT-based GUI</li>
<li>Web browser plugin</li>
<li>Web scrape text UI (to pull in a full page for offline reading)</li>
<li>IMAP/POP3/NNTP reading interface</li>
</ul></li>
<li>Content support:<ul>
<li>Plain text (rendered in the text UI and the GUI)</li>
<li>HTML (safe rendering within the GUI)</li>
</ul></li>
<li>Syndication:<ul>
<li>Feedspace, feedtree, or low latency purpose built syndication</li>
<li>Freenet (FCP put/get of individual .syndie files at CHK@ keys, with archives
stored and pulled from SSK/USK@ keys)</li>
<li>Email (post through SMTP/mixmaster/mixminion, read from procmail)</li>
</ul></li>
<li>Full text search with <a href="http://lucene.org/>">Lucene</a> integration</li>
<li><a href="http://www.hsqldb.org/">HSQLDB</a> full database encryption
(logs, data, backup, scripts, etc)</li>
<li>Archive management heuristics</li>
</ul>
</div>
</body>
</html>

538
doc/web/spec.html Normal file
View File

@ -0,0 +1,538 @@
<html>
<head>
<title>Syndie: spec</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<b>[dev]</b>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<div class="leftnav">
<a href="dev.html">[code]</a><br />
<b>[spec]</b><br />
<a href="archive.html">[archive]</a><br />
<a href="db.html">[database]</a><br />
</div>
<div class="bodycontent">
[<a id="specmenu" href="#message">Syndie messages</a>]
[<a href="#key">Syndie key files</a>]
[<a href="#uri">Syndie URIs</a>]
[<a href="#headers">Syndie message headers</a>]
<h2 id="message">Syndie messages (<a href="#specmenu">up</a>)</h2>
A <code>.syndie</code> file contains signed and potentially encrypted data for
passing Syndie channel metadata and posts around. It is made up of two parts- a
UTF-8 encoded header and a body. The header begins with a type line, followed by
name=value pairs, delimited by the newline character ('\n' or 0x0A). After
the pairs are complete, a blank newline is included, followed by the line
"Size=$numBytes\n", where $numBytes is the size of the body (base10). After that comes
that many bytes making up the body of the enclosed message, followed by two
newline delimited signature lines - AuthorizationSig=$signature and
AuthenticationSig=$signature. There can be any arbitrary amount of data after
the signature lines, but it is not currently interpreted.</p>
<p>The $numBytes body is an encrypted zip archive, though the encryption method
depends upon the type line. For posts and metadata messages, the data is
AES/256/CBC encrypted (with a 16 byte IV at the beginning). For private
messages, the first 512 bytes are ElGamal/2048 encrypted to the channel's
encryption key, which has the AES/256 session key and IV within it, and the
remaining bytes are AES/256/CBC encrypted.</p>
<p>The AES/256 encrypted area begins with a random number of nonzero padding
bytes, followed by 0x0, then the internal payload size (as a 4 byte unsigned
integer), followed by the total payload size (the same as the Size header),
followed by the actual Zip encoded data, a random number of pad bytes, up to
a 16 byte boundary, aka:</p>
<pre> rand(nonzero) padding + 0 + internalSize + totalSize + data + rand</pre>
<p>After the AES/256 encrypted area there is an HMAC-SHA256 of the body section,
using the SHA256 of the body decryption key concatenated with the IV as the
HMAC key.</p>
<p>The authorization signature is verified against the set of public keys
associated with the channel. Not all messages must have valid authorization
signatures, but unauthorized messages may not be passed along.</p>
<p>The authentication signature may be verified against the Author header (either
in the public or encrypted header sets), but not all messages are authenticated.</p>
<p>The unencrypted zip archive may contain the following entries:</p><ul>
<li><code>headers.dat</code> <i>[used in: posts, private messages, metadata posts]</i><p>
Optionally contains headers that are not visible to those who cannot decrypt
the message</p></li>
<li><code>page$n.dat</code> <i>[used in: posts, private messages]</i><p>
Page $n's contents</p></li>
<li><code>page$n.cfg</code> <i>[used in: posts, private messages]</i><p>
Headers for page $n: Content-type, title, references, etc</p></li>
<li><code>attach$n.dat</code> <i>[used in: posts, private messages]</i><p>
Attachment $n's contents</p></li>
<li><code>attach$n.cfg</code> <i>[used in: posts, private messages]</i><p>
Headers for attachment $n: Content-type, language, etc</p></li>
<li><code>avatar32.png</code> <i>[used in: posts, private messages, metadata posts]</i><p>
Contains a 32x32 pixel avatar for the message or channel</p></li>
<li><code>references.cfg</code> <i>[used in: posts, private messages, metadata posts]</i><p>
Contains a tree of syndie references, formatted as
"[\t]*$name\t$uri\t$refType\t$description\n", where the tab indentation
at the beginning of the line determines the tree structure. The refType
field can, for instance, be used to differentiate mentions of a positive
reference and those recommending blacklisting, etc.</p></li>
</ul>
<h2 id="key">Syndie key files (<a href="#specmenu">up</a>)</h2>
<p>When passing around keys for Syndie channels, they can either be transferred
in <a href="#uri">Syndie URIs</a> or in key files. The key files themselves
are UTF encoded as follows:</p>
<pre>
keytype: [manage|manage-pub|reply|reply-pub|post|post-pub|read]\n
scope: $base64(channelHash)\n
raw: $base64(bytes)\n
</pre>
<h2 id="uri">Syndie URIs (<a href="#specmenu">up</a>)</h2>
<p>This defines the URIs safely passable within syndie, capable of referencing
specific resources. They contain one of four reference types, plus a bencoded
set of attributes:</p>
<pre>
Type: url
Attributes:
* net: what network the URL is on, such as "i2p", "tor", "ip", or "freenet" (string)
* url: network-specific URL (string)
* name: [optional] short name of the resource referenced (string)
* desc: [optional] longer description of the resource (string)
* tag: [optional] list of tags attached to the reference (string[])
Type: channel
Attributes:
* channel: [1] base64 of the SHA256 of the channel's identity public key (string)
* author: [1] base64 of the SHA256 of the author's identity public key, if different from the channel (string)
* msgId: [1] unique identifier within the channel's scope (or author's scope, if specified) (integer)
* page: [optional] page within the message's scope (integer)
* attachment: [optional] attachment within the message's scope (integer)
* readKeyType: [optional] describes the readKey, e.g. "AES256" (string)
* readKeyData: [optional] base64 of the key required to read posts in the channel [string)
* postKeyType: [optional] describes the postKey, e.g. "DSA1024" (string)
* postKeyData: [optional] base64 of the private key required to post to the channel (string)
* name: [optional] short name of the resource referenced (string)
* desc: [optional] longer description of the resource (string)
* tag: [optional] list of tags attached to the reference (string[])
[1] If the field is not specified, it must be implicitly derived from the context.
For instance, a syndie post may omit the channel and msgId when referring to another
page or attachment on the current message.
Type: search
Attributes:
* channel: [optional] base64 of the SHA256 of the channel's identity public key (string)
* author: [optional] base64 of the SHA256 of the author's identity public key (string)
* tag: [optional] list of tags to match (string[])
* keyword: [optional] list of keywords to match (string[])
* age: [optional] number of days in the past to look back (integer)
* status: [optional] channels to look in- "new", "watched", "all" (string)
Type: archive
Attributes:
* net: what network the URL is on, such as "i2p", "tor", "ip", or "freenet" (string)
* url: network-specific URL (string)
* readKeyType: [optional] describes the readKey, e.g. "AES256" (string)
* readKeyData: [optional] base64 of the key required to pull data from the archive (string)
* postKeyType: [optional] describes the postKey, e.g. "AES256" (string)
* postKeyData: [optional] base64 of the key required to pull data from the archive (string)
* identKeyType: [optional] describes the identKey, e.g. "DSA1024" (string)
* identKeyData: [optional] base64 of the key the archive will identify themselves as (string)
* name: [optional] short name of the resource referenced (string)
* desc: [optional] longer description of the resource (string)
* tag: [optional] list of tags attached to the reference (string[])
Type: text
Attributes:
* name: [optional] short name of the freeform text reference (string)
* body: [optional] freeform text reference (string)
* tag: [optional] list of tags attached to the reference (string[])
The canonical encoding is: "urn:syndie:$refType:$bencodedAttributes",
with $refType being one of the five types above, and $bencodedAttributes
being the bencoded attributes. Strings are UTF-8, and the bencoded attributes
are ordered according to the UK locale (in the canonical form).
Examples:
urn:syndie:url:d3:url19:http://www.i2p.net/e
urn:syndie:channel:d7:channel40:12345678901234567890123456789012345678909:messageIdi42e4pagei0ee
urn:syndie:channel:d10:attachmenti3ee
urn:syndie:channel:d4:pagei2ee
urn:syndie:search:d3:tag3i2pe
urn:syndie:search:d6:status7:watchede
Within syndie-enabled apps, the urn:syndie: prefix can be dropped:
url:d3:url19:http://www.i2p.net/e
channel:d7:channel40:12345678901234567890123456789012345678909:messageIdi42e4pagei0ee
channel:d10:attachmenti3ee
channel:d4:pagei2ee
search:d3:tag3i2pe
search:d6:status7:watchede
</pre>
<h2 id="headers">Syndie message headers (<a href="#specmenu">up</a>)</h2>
<p>Syndie messages have a defined set of headers, and unknown headers are
uninterpreted.</p>
<p>
<a href="#header_author">Author</a>
<a href="#header_authenticationmask">AuthenticationMask</a>
<a href="#header_targetchannel">TargetChannel</a>
<a href="#header_posturi">PostURI</a>
<a href="#header_references">References</a>
<a href="#header_tags">Tags</a>
<a href="#header_overwriteuri">OverwriteURI</a>
<a href="#header_forcenewthread">ForceNewThread</a>
<a href="#header_refusereplies">RefuseReplies</a>
<a href="#header_cancel">Cancel</a>
<a href="#header_subject">Subject</a>
<a href="#header_bodykey">BodyKey</a>
<a href="#header_bodykeypromptsalt">BodyKeyPromptSalt</a>
<a href="#header_bodykeyprompt">BodyKeyPrompt</a>
<a href="#header_identity">Identity</a>
<a href="#header_encryptkey">EncryptKey</a>
<a href="#header_name">Name</a>
<a href="#header_description">Description</a>
<a href="#header_edition">Edition</a>
<a href="#header_publicposting">PublicPosting</a>
<a href="#header_publicreplies">PublicReplies</a>
<a href="#header_authorizedkeys">AuthorizedKeys</a>
<a href="#header_managerkeys">ManagerKeys</a>
<a href="#header_archives">Archives</a>
<a href="#header_channelreadkeys">ChannelReadKeys</a>
<a href="#header_expiration">Expiration</a> </p>
<p>In the following list, <i>Required</i> means the header must be included
for messages of the allowed types. <i>Allow as hidden</i> means the header
may be included in the encrypted <code>headers.dat</code> zip headers,
rather than in the unencrypted publicly visible headers. <i>Allow on posts</i>
means the header can be used on normal posts. <i>Allow on private messages</i>
means the header can be used on posts encrypted to a channel's private key.
<i>Allow on metadata messages</i> means the header can be used on metadata
messages configuring a channel.</p>
<ul>
<li id="header_author"><b>Author</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> base64(channel identity key)</li>
<li><b>Description:</b>
Nym that posted the message (must be authenticated with an authenticationSig)
</li>
</ul></li>
<li id="header_authenticationmask"><b>AuthenticationMask</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> base64(random 40 byte value)</li>
<li><b>Description:</b>
If included, the authenticationSignature is XORed against this mask before
checking for validity (preventing unauthorized people from seeing if the
post was authenticated by $nym by simply checking the signature against
$nym's pubkey)
</li>
</ul></li>
<li id="header_targetchannel"><b>TargetChannel</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> base64(channel ident hash)</li>
<li><b>Description:</b>
The channel in the PostURI may refer to the author (eg when unauthorized),
so this supplements that with the channel that the post is destined for
</li>
</ul></li>
<li id="header_posturi"><b>PostURI</b><ul>
<li><b>Required?</b> yes</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> <a href="#uri">Syndie URI</a></li>
<li><b>Description:</b>
Unique message identifier, including the channel the post is authorized
for and the messageId. If the post is not authorized for the
<a href="#header_targetchannel">target channel</a>, the scope referenced is
the <a href="#header_author">author's</a>
</li>
</ul></li>
<li id="header_references"><b>References</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> comma delimited list of <a href="#uri">Syndie URI</a>s</li>
<li><b>Description:</b>
Earliest in the list is most recent ancestor
</li>
</ul></li>
<li id="header_tags"><b>Tags</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> tab delimited list of tags</li>
<li><b>Description:</b>
Key phrases describing the message or channel
</li>
</ul></li>
<li id="header_overwriteuri"><b>OverwriteURI</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> <a href="#uri">Syndie URI</a></li>
<li><b>Description:</b>
Entry that the current post is replacing (if authorized)
</li>
</ul></li>
<li id="header_forcenewthread"><b>ForceNewThread</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> "true" or "false"</li>
<li><b>Description:</b>
If true, this message starts a new thread, even if there are parents
</li>
</ul></li>
<li id="header_refusereplies"><b>RefuseReplies</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> "true" or "false"</li>
<li><b>Description:</b>
If true, only the author may post a threaded reply to this post
</li>
</ul></li>
<li id="header_cancel"><b>Cancel</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> tab delimited list of <a href="#uri">Syndie URIs</a></li>
<li><b>Description:</b>
If the author is allowed, cancel the given message(s)
</li>
</ul></li>
<li id="header_subject"><b>Subject</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> no</li>
<li><b>Content type:</b> text</li>
<li><b>Description:</b>
Short description of what the post is about
</li>
</ul></li>
<li id="header_bodykey"><b>BodyKey</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> base64(AES256 session key)</li>
<li><b>Description:</b>
If specified, the zip entries are encrypted with the given key (and
hence, readable by anyone) rather than the channel's normal
(unpublicized) session key.
</li>
</ul></li>
<li id="header_bodykeypromptsalt"><b>BodyKeyPromptSalt</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> base64(channel identity key)</li>
<li><b>Description:</b>
Fed into the PBE algorithm with the answer to the <a href="#header_bodykeyprompt">BodyKeyPrompt</a>
</li>
</ul></li>
<li id="header_bodykeyprompt"><b>BodyKeyPrompt</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> text</li>
<li><b>Description:</b>
If specified, the body is encrypted with a PBE key generated by answering
this query correctly. e.g. "BodyKeyPrompt=1+1=?" would PBE generate the
body key from the correct answer ("2")
</li>
</ul></li>
<li id="header_identity"><b>Identity</b><ul>
<li><b>Required?</b> yes</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> base64(DSA1024 public key)</li>
<li><b>Description:</b>
Master signing public key
</li>
</ul></li>
<li id="header_encryptkey"><b>EncryptKey</b><ul>
<li><b>Required?</b> yes</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> base64(Elgamal2048 public key)</li>
<li><b>Description:</b>
For private replies
</li>
</ul></li>
<li id="header_name"><b>Name</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> text</li>
<li><b>Description:</b>
Suggested nickname
</li>
</ul></li>
<li id="header_description"><b>Description</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> text</li>
<li><b>Description:</b>
Suggested description
</li>
</ul></li>
<li id="header_edition"><b>Edition</b><ul>
<li><b>Required?</b> yes</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> base10 nonnegative integer</li>
<li><b>Description:</b>
Higher numbers replace lower numbers
</li>
</ul></li>
<li id="header_publicposting"><b>PublicPosting</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> "true" or "false"</li>
<li><b>Description:</b>
If true, authenticated posts do not need to be authorized (though
authorized posts can be unauthenticated)
</li>
</ul></li>
<li id="header_publicreplies"><b>PublicReplies</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> "true" or "false"</li>
<li><b>Description:</b>
Like AllowPublicPosting, except replies only (unauthorized users
can't create new threads)
</li>
</ul></li>
<li id="header_authorizedkeys"><b>AuthorizedKeys</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> tab delimited base64(DSA1024 public key)</li>
<li><b>Description:</b>
Posts with authorizationSignatures verified by one of these keys are
allowed (the pubkeys may be a nym's identity key or may be a separate
signing key shared by multiple authors)
</li>
</ul></li>
<li id="header_managerkeys"><b>ManagerKeys</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> no</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> tab delimited base64(DSA1024 public key)</li>
<li><b>Description:</b>
New metadata messages for this channel may be signed by one of
these keys (the channel's identity key is implicitly in this set)
</li>
</ul></li>
<li id="header_archives"><b>Archives</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> tab delimited <a href="#uri">Syndie URIs</a></li>
<li><b>Description:</b>
Lists some archives that may be consulted for new posts in the
current channel (and/or which may accept replies for the channel)
</li>
</ul></li>
<li id="header_channelreadkeys"><b>ChannelReadKeys</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> no</li>
<li><b>Allow on private messages?</b> no</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> tab delimited base64(AES256 key)</li>
<li><b>Description:</b>
Adds some channel read keys that may be used to encrypt/decrypt
posts in the channel (publicizing these makes the channel publicly readable)
</li>
</ul></li>
<li id="header_expiration"><b>Expiration</b><ul>
<li><b>Required?</b> no</li>
<li><b>Allow as hidden?</b> yes</li>
<li><b>Allow on posts?</b> yes</li>
<li><b>Allow on private messages?</b> yes</li>
<li><b>Allow on metadata messages?</b> yes</li>
<li><b>Content type:</b> YYYYMMDD</li>
<li><b>Description:</b>
Suggest a date after which the channel / message can be discarded safely
</li>
</ul></li>
</ul>
<p>When referring to <code>base64</code>, the content is base64 encoded
with an alternate alphabet. The alphabet is the standard one except with
"~" replacing "/", and "+" with "-" (for safer URL and file name encoding).
</p>
</div>
</body>
</html>

27
doc/web/style.css Normal file
View File

@ -0,0 +1,27 @@
body {
background-color: #eee;
font-family: Helvetica, sans-serif;
}
div.topnav {
float: top;
left: 1em;
top: 1em;
margin: 0em 1em;
background-color: #ddd;
text-align: center;
border: medium dashed;
}
div.leftnav {
float: left;
left: 1em;
top: 1em;
margin: 0em 1em;
background-color: #ddd;
text-align: left;
border-left: medium dashed;
border-right: medium dashed;
border-bottom: medium dashed;
}
div.bodycontent {
margin: 1em 1em;
}

BIN
doc/web/syndie_nets.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

88
doc/web/usecases.html Normal file
View File

@ -0,0 +1,88 @@
<html>
<head>
<title>Syndie: Use cases</title>
<link rel="stylesheet" href="style.css" type="text/css" title="normal" media="screen" />
</head>
<body>
<div style="display: none"><a href="#bodycontent" title="Skip navigation" accesskey="2">Skip navigation</a></div>
<div class="topnav">
<a href="index.html">[home]</a>
<a href="download.html">[download]</a>
<a href="features.html">[features]</a>
<a href="manual.html">[manual]</a>
<a href="http://forum.i2p.net/viewforum.php?f=29">[forum]</a>
<a href="faq.html">[faq]</a>
<a href="roadmap.html">[roadmap]</a>
<a href="dev.html">[dev]</a>
<a href="donate.html">[donate]</a>
<a href="about.html">[about]</a>
</div>
<!--
<div class="leftnav">
left nav
</div>
-->
<div class="bodycontent">
<h3>Syndie use cases (aka "why you would use Syndie")</h3>
<ul id="menu">
<li><a href="#decentralizedforum">Decentralized forum</a></li>
<li><a href="#privateforum">Private forum</a></li>
<li><a href="#offlineforum">Offline forum participation</a></li>
<li><a href="#offlinebrowsing">Offline browsing</a></li>
<li><a href="#securereader">Secure reader</a></li>
</ul>
<b id="decentralizedforum">Decentralized forum</b> (<a href="#menu">up</a>)
<p>While many different groups often want to organize discussions into an
online forum, the centralized nature of traditional forums (websites, BBSes,
etc) can be a problem. For instance, the site hosting the forum can be
taken offline through <b>denial of service attacks</b> or administrative action.
In addition, the single host offers a simple point to <b>monitor the group's
activity</b>, so that even if a forum is pseudonymous, those pseudonyms can be
tied to the IP that posted or read individual messages.</p>
<p>In addition, not only are the forums decentralized, they are organized
in an ad-hoc manner yet fully compatible with other organization techniques.
This means that some small group of people can run their forum using one
technique (distributing the messages by pasting them on a wiki site),
another can run their forum using another technique (posting their messages
in a distributed hashtable like <a href="http://www.opendht.org/">OpenDHT</a>,
yet if one person is aware of both techniques, they can synchronize the two
forums together. This lets the people who were only aware of the wiki site
talk to people who were only aware of the OpenDHT service without knowing
anything about each other. Extended further, <b>Syndie allows individual
cells to control their own exposure while communicating across the whole
organization</b>.</p>
<b id="privateforum">Private forum</b> (<a href="#menu">up</a>)
<p>Forums can be configured so that only authorized people can read the
content, or even know what pseudonyms are posting the messages, even if an
adversary confiscates the servers distributing the posts. In addition,
authorized users can prevent any unauthorized posts from being made entirely,
or only allow them under limited circumstances.</p>
<b id="offlineforum">Offline forum participation</b> (<a href="#menu">up</a>)
<p>Unlike traditional forums, with Syndie you can particpate even when you
are not online, "syncing up" any accumulated changes with the forum later
on when it is convenient, perhaps days, weeks, or even months later.</p>
<b id="offlinebrowsing">Offline browsing</b> (<a href="#menu">up</a>)
<p>Syndie is not limited to simple text messages - individual web pages or
full web sites can be packaged up into a single Syndie post, and using the
<a href="#offlineforum">offline forum</a> functionality, you can browse that
web site through Syndie without an active internet connection.</p>
<b id="securereader">Secure reader</b> (<a href="#menu">up</a>)
<p>All applications strive for security, but most do not consider
identity or traffic pattern related information sensitive, so they do not
bother trying to control their exposure. Syndie however is designed with
the needs of people demanding strong anonymity and security in mind.</p>
</div>
</body>
</html>

BIN
lib/hsqldb.jar Normal file

Binary file not shown.

57
lib/hsqldb_changes.txt Normal file
View File

@ -0,0 +1,57 @@
hsqldb.jar is HSQLDB 1.8.0.5 built with switchtojdk14
In addition, the following source changes were made:
--- ../hsqldb/src/org/hsqldb/Database.java 2006-04-11 14:40:53.000000000 +0000
+++ ../hsqldb_build/src/org/hsqldb/Database.java 2006-08-29 13:49:34.000000000 +0000
@@ -313,6 +313,7 @@
clearStructures();
DatabaseManager.removeDatabase(this);
+ e.printStackTrace();
if (!(e instanceof HsqlException)) {
e = Trace.error(Trace.GENERAL_ERROR, e.toString());
}
--- ../hsqldb/src/org/hsqldb/jdbc/jdbcConnection.java 2006-07-07 23:09:07.000000000 +0000
+++ ../hsqldb_build/src/org/hsqldb/jdbc/jdbcConnection.java 2006-08-29 13:44:26.000000000 +0000
@@ -2393,6 +2393,7 @@
connProperties = props;
} catch (HsqlException e) {
+ e.printStackTrace();
throw Util.sqlException(e);
}
}
--- ../hsqldb/src/org/hsqldb/jdbc/jdbcConnection.java 2006-07-07 23:09:07.000000000 +0000
+++ ../hsqldb_build/src/org/hsqldb/jdbc/jdbcConnection.java 2006-08-29 13:44:26.000000000 +0000
@@ -2393,6 +2393,7 @@
connProperties = props;
} catch (HsqlException e) {
+ e.printStackTrace();
throw Util.sqlException(e);
}
}
--- ../hsqldb/src/org/hsqldb/persist/LockFile.java 2006-07-16 22:29:33.000000000 +0000
+++ ../hsqldb_build/src/org/hsqldb/persist/LockFile.java 2006-08-29 14:17:41.000000000 +0000
@@ -554,6 +554,9 @@
c = null;
+ if (false) { // jr: avoid nio for gcj 3.4.*, even though it exists, its not implemented
+ lf = new LockFile();
+ } else {
try {
Class.forName("java.nio.channels.FileLock");
@@ -562,6 +565,7 @@
} catch (Exception e) {
lf = new LockFile();
}
+ }
f = new File(path);

31
lib/hsqldb_lic.txt Normal file
View File

@ -0,0 +1,31 @@
/* Copyright (c) 2001-2005, The HSQL Development Group
* 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 the HSQL Development Group 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 HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* 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.
*/

BIN
lib/servlet.jar Normal file

Binary file not shown.

3
logger.config Normal file
View File

@ -0,0 +1,3 @@
logger.record.syndie=DEBUG
logger.record.net.i2p.util.EepPost=DEBUG
logger.minimumOnScreenLevel=ERROR

541
nbproject/build-impl.xml Normal file
View File

@ -0,0 +1,541 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
*** GENERATED FROM project.xml - DO NOT EDIT ***
*** EDIT ../build.xml INSTEAD ***
For the purpose of easier reading the script
is divided into following sections:
- initialization
- compilation
- jar
- execution
- debugging
- javadoc
- junit compilation
- junit execution
- junit debugging
- applet
- cleanup
-->
<project name="syndie-impl" default="default" basedir=".." xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:j2seproject2="http://www.netbeans.org/ns/j2se-project/2" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:jaxws="http://www.netbeans.org/ns/jax-ws/1">
<target name="default" depends="test,jar,javadoc" description="Build and test whole project."/>
<!--
======================
INITIALIZATION SECTION
======================
-->
<target name="-pre-init">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-init-private" depends="-pre-init">
<property file="nbproject/private/private.properties"/>
</target>
<target name="-init-user" depends="-pre-init,-init-private">
<property file="${user.properties.file}"/>
<!-- The two properties below are usually overridden -->
<!-- by the active platform. Just a fallback. -->
<property name="default.javac.source" value="1.4"/>
<property name="default.javac.target" value="1.4"/>
</target>
<target name="-init-project" depends="-pre-init,-init-private,-init-user">
<property file="nbproject/project.properties"/>
</target>
<target name="-do-init" depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property">
<available file="${manifest.file}" property="manifest.available"/>
<condition property="manifest.available+main.class">
<and>
<isset property="manifest.available"/>
<isset property="main.class"/>
<not>
<equals arg1="${main.class}" arg2="" trim="true"/>
</not>
</and>
</condition>
<condition property="manifest.available+main.class+mkdist.available">
<and>
<istrue value="${manifest.available+main.class}"/>
<isset property="libs.CopyLibs.classpath"/>
</and>
</condition>
<condition property="have.tests">
<or>
<available file="${test.src.dir}"/>
</or>
</condition>
<condition property="have.sources">
<or>
<available file="${src.dir}"/>
</or>
</condition>
<condition property="netbeans.home+have.tests">
<and>
<isset property="netbeans.home"/>
<isset property="have.tests"/>
</and>
</condition>
<condition property="no.javadoc.preview">
<isfalse value="${javadoc.preview}"/>
</condition>
<property name="run.jvmargs" value=""/>
<property name="javac.compilerargs" value=""/>
<property name="work.dir" value="${basedir}"/>
<condition property="no.deps">
<and>
<istrue value="${no.dependencies}"/>
</and>
</condition>
<property name="javac.debug" value="true"/>
<property name="javadoc.preview" value="true"/>
</target>
<target name="-post-init">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-init-check" depends="-pre-init,-init-private,-init-user,-init-project,-do-init">
<fail unless="src.dir">Must set src.dir</fail>
<fail unless="test.src.dir">Must set test.src.dir</fail>
<fail unless="build.dir">Must set build.dir</fail>
<fail unless="dist.dir">Must set dist.dir</fail>
<fail unless="build.classes.dir">Must set build.classes.dir</fail>
<fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
<fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
<fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
<fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
<fail unless="dist.jar">Must set dist.jar</fail>
</target>
<target name="-init-macrodef-property">
<macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
<attribute name="name"/>
<attribute name="value"/>
<sequential>
<property name="@{name}" value="${@{value}}"/>
</sequential>
</macrodef>
</target>
<target name="-init-macrodef-javac">
<macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
<attribute name="srcdir" default="${src.dir}"/>
<attribute name="destdir" default="${build.classes.dir}"/>
<attribute name="classpath" default="${javac.classpath}"/>
<attribute name="debug" default="${javac.debug}"/>
<element name="customize" optional="true"/>
<sequential>
<javac srcdir="@{srcdir}" destdir="@{destdir}" debug="@{debug}" deprecation="${javac.deprecation}" source="${javac.source}" target="${javac.target}" includeantruntime="false">
<classpath>
<path path="@{classpath}"/>
</classpath>
<compilerarg line="${javac.compilerargs}"/>
<customize/>
</javac>
</sequential>
</macrodef>
</target>
<target name="-init-macrodef-junit">
<macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
<attribute name="includes" default="**/*Test.java"/>
<sequential>
<junit showoutput="true" fork="true" dir="${basedir}" failureproperty="tests.failed" errorproperty="tests.failed">
<batchtest todir="${build.test.results.dir}">
<fileset dir="${test.src.dir}" includes="@{includes}"/>
</batchtest>
<classpath>
<path path="${run.test.classpath}"/>
</classpath>
<syspropertyset>
<propertyref prefix="test-sys-prop."/>
<mapper type="glob" from="test-sys-prop.*" to="*"/>
</syspropertyset>
<formatter type="brief" usefile="false"/>
<formatter type="xml"/>
<jvmarg line="${run.jvmargs}"/>
</junit>
</sequential>
</macrodef>
</target>
<target name="-init-macrodef-nbjpda">
<macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
<attribute name="name" default="${main.class}"/>
<attribute name="classpath" default="${debug.classpath}"/>
<attribute name="stopclassname" default=""/>
<sequential>
<nbjpdastart transport="dt_socket" addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}">
<classpath>
<path path="@{classpath}"/>
</classpath>
</nbjpdastart>
</sequential>
</macrodef>
<macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
<attribute name="dir" default="${build.classes.dir}"/>
<sequential>
<nbjpdareload>
<fileset includes="${fix.includes}*.class" dir="@{dir}"/>
</nbjpdareload>
</sequential>
</macrodef>
</target>
<target name="-init-macrodef-debug">
<macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
<attribute name="classname" default="${main.class}"/>
<attribute name="classpath" default="${debug.classpath}"/>
<element name="customize" optional="true"/>
<sequential>
<java fork="true" classname="@{classname}" dir="${work.dir}">
<jvmarg value="-Xdebug"/>
<jvmarg value="-Xnoagent"/>
<jvmarg value="-Djava.compiler=none"/>
<jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
<jvmarg line="${run.jvmargs}"/>
<classpath>
<path path="@{classpath}"/>
</classpath>
<syspropertyset>
<propertyref prefix="run-sys-prop."/>
<mapper type="glob" from="run-sys-prop.*" to="*"/>
</syspropertyset>
<customize/>
</java>
</sequential>
</macrodef>
</target>
<target name="-init-macrodef-java">
<macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
<attribute name="classname" default="${main.class}"/>
<element name="customize" optional="true"/>
<sequential>
<java fork="true" classname="@{classname}" dir="${work.dir}">
<jvmarg line="${run.jvmargs}"/>
<classpath>
<path path="${run.classpath}"/>
</classpath>
<syspropertyset>
<propertyref prefix="run-sys-prop."/>
<mapper type="glob" from="run-sys-prop.*" to="*"/>
</syspropertyset>
<customize/>
</java>
</sequential>
</macrodef>
</target>
<target name="-init-presetdef-jar">
<presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
<jar jarfile="${dist.jar}" compress="${jar.compress}">
<j2seproject1:fileset dir="${build.classes.dir}"/>
</jar>
</presetdef>
</target>
<target name="init" depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-junit,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar"/>
<!--
===================
COMPILATION SECTION
===================
-->
<target name="deps-jar" depends="init" unless="no.deps"/>
<target name="-pre-pre-compile" depends="init,deps-jar">
<mkdir dir="${build.classes.dir}"/>
</target>
<target name="-pre-compile">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-do-compile" depends="init,deps-jar,-pre-pre-compile,-pre-compile" if="have.sources">
<j2seproject3:javac/>
<copy todir="${build.classes.dir}">
<fileset dir="${src.dir}" excludes="${build.classes.excludes}"/>
</copy>
</target>
<target name="-post-compile">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="compile" depends="init,deps-jar,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project."/>
<target name="-pre-compile-single">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-do-compile-single" depends="init,deps-jar,-pre-pre-compile">
<fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
<j2seproject3:javac>
<customize>
<patternset includes="${javac.includes}"/>
</customize>
</j2seproject3:javac>
</target>
<target name="-post-compile-single">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="compile-single" depends="init,deps-jar,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single"/>
<!--
====================
JAR BUILDING SECTION
====================
-->
<target name="-pre-pre-jar" depends="init">
<dirname property="dist.jar.dir" file="${dist.jar}"/>
<mkdir dir="${dist.jar.dir}"/>
</target>
<target name="-pre-jar">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-do-jar-without-manifest" depends="init,compile,-pre-pre-jar,-pre-jar" unless="manifest.available">
<j2seproject1:jar/>
</target>
<target name="-do-jar-with-manifest" depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available" unless="manifest.available+main.class">
<j2seproject1:jar manifest="${manifest.file}"/>
</target>
<target name="-do-jar-with-mainclass" depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available+main.class" unless="manifest.available+main.class+mkdist.available">
<j2seproject1:jar manifest="${manifest.file}">
<j2seproject1:manifest>
<j2seproject1:attribute name="Main-Class" value="${main.class}"/>
</j2seproject1:manifest>
</j2seproject1:jar>
<echo>To run this application from the command line without Ant, try:</echo>
<property name="build.classes.dir.resolved" location="${build.classes.dir}"/>
<property name="dist.jar.resolved" location="${dist.jar}"/>
<pathconvert property="run.classpath.with.dist.jar">
<path path="${run.classpath}"/>
<map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
</pathconvert>
<echo>java -cp "${run.classpath.with.dist.jar}" ${main.class}</echo>
</target>
<target name="-do-jar-with-libraries" depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available+main.class+mkdist.available">
<property name="build.classes.dir.resolved" location="${build.classes.dir}"/>
<pathconvert property="run.classpath.without.build.classes.dir">
<path path="${run.classpath}"/>
<map from="${build.classes.dir.resolved}" to=""/>
</pathconvert>
<pathconvert property="jar.classpath" pathsep=" ">
<path path="${run.classpath.without.build.classes.dir}"/>
<chainedmapper>
<flattenmapper/>
<globmapper from="*" to="lib/*"/>
</chainedmapper>
</pathconvert>
<taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" name="copylibs" classpath="${libs.CopyLibs.classpath}"/>
<copylibs manifest="${manifest.file}" runtimeclasspath="${run.classpath.without.build.classes.dir}" jarfile="${dist.jar}" compress="${jar.compress}">
<fileset dir="${build.classes.dir}"/>
<manifest>
<attribute name="Main-Class" value="${main.class}"/>
<attribute name="Class-Path" value="${jar.classpath}"/>
</manifest>
</copylibs>
<echo>To run this application from the command line without Ant, try:</echo>
<property name="dist.jar.resolved" location="${dist.jar}"/>
<echo>java -jar "${dist.jar.resolved}"</echo>
</target>
<target name="-post-jar">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="jar" depends="init,compile,-pre-jar,-do-jar-with-manifest,-do-jar-without-manifest,-do-jar-with-mainclass,-do-jar-with-libraries,-post-jar" description="Build JAR."/>
<!--
=================
EXECUTION SECTION
=================
-->
<target name="run" depends="init,compile" description="Run a main class.">
<j2seproject1:java>
<customize>
<arg line="${application.args}"/>
</customize>
</j2seproject1:java>
</target>
<target name="run-single" depends="init,compile-single">
<fail unless="run.class">Must select one file in the IDE or set run.class</fail>
<j2seproject1:java classname="${run.class}"/>
</target>
<!--
=================
DEBUGGING SECTION
=================
-->
<target name="-debug-start-debugger" if="netbeans.home" depends="init">
<j2seproject1:nbjpdastart name="${debug.class}"/>
</target>
<target name="-debug-start-debuggee" depends="init,compile">
<j2seproject3:debug>
<customize>
<arg line="${application.args}"/>
</customize>
</j2seproject3:debug>
</target>
<target name="debug" if="netbeans.home" depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE."/>
<target name="-debug-start-debugger-stepinto" if="netbeans.home" depends="init">
<j2seproject1:nbjpdastart stopclassname="${main.class}"/>
</target>
<target name="debug-stepinto" if="netbeans.home" depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee"/>
<target name="-debug-start-debuggee-single" if="netbeans.home" depends="init,compile-single">
<fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
<j2seproject3:debug classname="${debug.class}"/>
</target>
<target name="debug-single" if="netbeans.home" depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single"/>
<target name="-pre-debug-fix" depends="init">
<fail unless="fix.includes">Must set fix.includes</fail>
<property name="javac.includes" value="${fix.includes}.java"/>
</target>
<target name="-do-debug-fix" if="netbeans.home" depends="init,-pre-debug-fix,compile-single">
<j2seproject1:nbjpdareload/>
</target>
<target name="debug-fix" if="netbeans.home" depends="init,-pre-debug-fix,-do-debug-fix"/>
<!--
===============
JAVADOC SECTION
===============
-->
<target name="-javadoc-build" depends="init">
<mkdir dir="${dist.javadoc.dir}"/>
<javadoc destdir="${dist.javadoc.dir}" source="${javac.source}" notree="${javadoc.notree}" use="${javadoc.use}" nonavbar="${javadoc.nonavbar}" noindex="${javadoc.noindex}" splitindex="${javadoc.splitindex}" author="${javadoc.author}" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}" private="${javadoc.private}" additionalparam="${javadoc.additionalparam}" failonerror="true" useexternalfile="true">
<classpath>
<path path="${javac.classpath}"/>
</classpath>
<sourcepath>
<pathelement location="${src.dir}"/>
</sourcepath>
<packageset dir="${src.dir}" includes="*/**"/>
<fileset dir="${src.dir}" includes="*.java"/>
</javadoc>
</target>
<target name="-javadoc-browse" if="netbeans.home" unless="no.javadoc.preview" depends="init,-javadoc-build">
<nbbrowse file="${dist.javadoc.dir}/index.html"/>
</target>
<target name="javadoc" depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc."/>
<!--
=========================
JUNIT COMPILATION SECTION
=========================
-->
<target name="-pre-pre-compile-test" if="have.tests" depends="init,compile">
<mkdir dir="${build.test.classes.dir}"/>
</target>
<target name="-pre-compile-test">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-do-compile-test" if="have.tests" depends="init,compile,-pre-pre-compile-test,-pre-compile-test">
<j2seproject3:javac srcdir="${test.src.dir}" destdir="${build.test.classes.dir}" debug="true" classpath="${javac.test.classpath}"/>
<copy todir="${build.test.classes.dir}">
<fileset dir="${test.src.dir}" excludes="**/*.java"/>
</copy>
</target>
<target name="-post-compile-test">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="compile-test" depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test"/>
<target name="-pre-compile-test-single">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-do-compile-test-single" if="have.tests" depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single">
<fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
<j2seproject3:javac srcdir="${test.src.dir}" destdir="${build.test.classes.dir}" debug="true" classpath="${javac.test.classpath}">
<customize>
<patternset includes="${javac.includes}"/>
</customize>
</j2seproject3:javac>
<copy todir="${build.test.classes.dir}">
<fileset dir="${test.src.dir}" excludes="**/*.java"/>
</copy>
</target>
<target name="-post-compile-test-single">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="compile-test-single" depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single"/>
<!--
=======================
JUNIT EXECUTION SECTION
=======================
-->
<target name="-pre-test-run" if="have.tests" depends="init">
<mkdir dir="${build.test.results.dir}"/>
</target>
<target name="-do-test-run" if="have.tests" depends="init,compile-test,-pre-test-run">
<j2seproject3:junit/>
</target>
<target name="-post-test-run" if="have.tests" depends="init,compile-test,-pre-test-run,-do-test-run">
<fail if="tests.failed">Some tests failed; see details above.</fail>
</target>
<target name="test-report" if="have.tests" depends="init"/>
<target name="-test-browse" if="netbeans.home+have.tests" depends="init"/>
<target name="test" depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests."/>
<target name="-pre-test-run-single" if="have.tests" depends="init">
<mkdir dir="${build.test.results.dir}"/>
</target>
<target name="-do-test-run-single" if="have.tests" depends="init,compile-test-single,-pre-test-run-single">
<fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
<j2seproject3:junit includes="${test.includes}"/>
</target>
<target name="-post-test-run-single" if="have.tests" depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single">
<fail if="tests.failed">Some tests failed; see details above.</fail>
</target>
<target name="test-single" depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test."/>
<!--
=======================
JUNIT DEBUGGING SECTION
=======================
-->
<target name="-debug-start-debuggee-test" if="have.tests" depends="init,compile-test">
<fail unless="test.class">Must select one file in the IDE or set test.class</fail>
<j2seproject3:debug classname="junit.textui.TestRunner" classpath="${debug.test.classpath}">
<customize>
<arg line="${test.class}"/>
</customize>
</j2seproject3:debug>
</target>
<target name="-debug-start-debugger-test" if="netbeans.home+have.tests" depends="init,compile-test">
<j2seproject1:nbjpdastart name="${test.class}" classpath="${debug.test.classpath}"/>
</target>
<target name="debug-test" depends="init,compile-test,-debug-start-debugger-test,-debug-start-debuggee-test"/>
<target name="-do-debug-fix-test" if="netbeans.home" depends="init,-pre-debug-fix,compile-test-single">
<j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
</target>
<target name="debug-fix-test" if="netbeans.home" depends="init,-pre-debug-fix,-do-debug-fix-test"/>
<!--
=========================
APPLET EXECUTION SECTION
=========================
-->
<target name="run-applet" depends="init,compile-single">
<fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
<j2seproject1:java classname="sun.applet.AppletViewer">
<customize>
<arg value="${applet.url}"/>
</customize>
</j2seproject1:java>
</target>
<!--
=========================
APPLET DEBUGGING SECTION
=========================
-->
<target name="-debug-start-debuggee-applet" if="netbeans.home" depends="init,compile-single">
<fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
<j2seproject3:debug classname="sun.applet.AppletViewer">
<customize>
<arg value="${applet.url}"/>
</customize>
</j2seproject3:debug>
</target>
<target name="debug-applet" if="netbeans.home" depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet"/>
<!--
===============
CLEANUP SECTION
===============
-->
<target name="deps-clean" depends="init" unless="no.deps"/>
<target name="-do-clean" depends="init">
<delete dir="${build.dir}"/>
<delete dir="${dist.dir}"/>
</target>
<target name="-post-clean">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="clean" depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products."/>
</project>

View File

@ -0,0 +1,11 @@
build.xml.data.CRC32=44a7686e
build.xml.script.CRC32=2c41fa2f
build.xml.stylesheet.CRC32=d5b6853a
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=05a588e0
nbproject/build-impl.xml.script.CRC32=4f12d7eb
nbproject/build-impl.xml.stylesheet.CRC32=f30dc97d
nbproject/profiler-build-impl.xml.data.CRC32=44a7686e
nbproject/profiler-build-impl.xml.script.CRC32=abda56ed
nbproject/profiler-build-impl.xml.stylesheet.CRC32=a5b6598e

View File

@ -0,0 +1 @@
user.properties.file=/home/jrandom/.netbeans/5.5beta2/build.properties

View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
*** GENERATED FROM project.xml - DO NOT EDIT ***
*** EDIT ../build.xml INSTEAD ***
For the purpose of easier reading the script
is divided into following sections:
- initialization
- profiling
- applet profiling
-->
<project name="-profiler-impl" default="profile" basedir="..">
<target name="default" depends="profile" description="Build and profile the project."/>
<!--
======================
INITIALIZATION SECTION
======================
-->
<target name="profile-init" depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check"/>
<target name="-profile-pre-init">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-profile-post-init">
<!-- Empty placeholder for easier customization. -->
<!-- You can override this target in the ../build.xml file. -->
</target>
<target name="-profile-init-macrodef-profile">
<macrodef name="resolve">
<attribute name="name"/>
<attribute name="value"/>
<sequential>
<property name="@{name}" value="${env.@{value}}"/>
</sequential>
</macrodef>
<macrodef name="profile">
<attribute name="classname" default="${main.class}"/>
<element name="customize" optional="true"/>
<sequential>
<property environment="env"/>
<resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
<java fork="true" classname="@{classname}" dir="${profiler.info.dir}" jvm="${profiler.info.jvm}">
<jvmarg value="${profiler.info.jvmargs.agent}"/>
<jvmarg line="${profiler.info.jvmargs}"/>
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
<arg line="${application.args}"/>
<classpath>
<path path="${run.classpath}"/>
</classpath>
<syspropertyset>
<propertyref prefix="run-sys-prop."/>
<mapper type="glob" from="run-sys-prop.*" to="*"/>
</syspropertyset>
<customize/>
</java>
</sequential>
</macrodef>
</target>
<target name="-profile-init-check" depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile">
<fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail>
<fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail>
</target>
<!--
=================
PROFILING SECTION
=================
-->
<target name="profile" if="netbeans.home" depends="profile-init,compile" description="Profile a project in the IDE.">
<nbprofiledirect>
<classpath>
<path path="${run.classpath}"/>
</classpath>
</nbprofiledirect>
<profile/>
</target>
<target name="profile-single" if="netbeans.home" depends="profile-init,compile-single" description="Profile a selected class in the IDE.">
<fail unless="profile.class">Must select one file in the IDE or set profile.class</fail>
<nbprofiledirect>
<classpath>
<path path="${run.classpath}"/>
</classpath>
</nbprofiledirect>
<profile classname="${profile.class}"/>
</target>
<!--
=========================
APPLET PROFILING SECTION
=========================
-->
<target name="profile-applet" if="netbeans.home" depends="profile-init,compile-single">
<nbprofiledirect>
<classpath>
<path path="${run.classpath}"/>
</classpath>
</nbprofiledirect>
<profile classname="sun.applet.AppletViewer">
<customize>
<arg value="${applet.url}"/>
</customize>
</profile>
</target>
<!--
=========================
TESTS PROFILING SECTION
=========================
-->
<target name="profile-test-single" if="netbeans.home" depends="profile-init,compile-test-single">
<nbprofiledirect>
<classpath>
<path path="${run.test.classpath}"/>
</classpath>
</nbprofiledirect>
<junit showoutput="true" fork="true" dir="${profiler.info.dir}" jvm="${profiler.info.jvm}" failureproperty="tests.failed" errorproperty="tests.failed">
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
<jvmarg value="${profiler.info.jvmargs.agent}"/>
<jvmarg line="${profiler.info.jvmargs}"/>
<test name="${profile.class}"/>
<classpath>
<path path="${run.test.classpath}"/>
</classpath>
<syspropertyset>
<propertyref prefix="test-sys-prop."/>
<mapper type="glob" from="test-sys-prop.*" to="*"/>
</syspropertyset>
<formatter type="brief" usefile="false"/>
<formatter type="xml"/>
</junit>
</target>
</project>

View File

@ -0,0 +1,58 @@
application.args=
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form,**/.svn
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
debug.classpath=\
${run.classpath}
debug.test.classpath=\
${run.test.classpath}
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=lib/syndie.jar
dist.javadoc.dir=dist/javadoc
file.reference.hsqldb.jar=lib/hsqldb.jar
jar.compress=false
javac.classpath=\
${file.reference.hsqldb.jar}
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.source=1.4
javac.target=1.4
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}:\
${libs.junit.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=true
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
main.class=syndie.db.TextUI
manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
${build.classes.dir}
# Space-separated list of JVM arguments used when running the project
# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
# or test-sys-prop.name=value to set system properties for unit tests):
run.jvmargs=
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
src.dir=src
test.src.dir=test

16
nbproject/project.xml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>syndie</name>
<minimum-ant-version>1.6.5</minimum-ant-version>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<shortcuts>
<skipIfNotSupported />
<programGroup defaultName="Syndie" location="applications" />
<shortcut name="Syndie"
target="$INSTALL_PATH/bin/syndie"
commandLine=""
workingDirectory="$INSTALL_PATH"
initialState="normal"
programGroup="yes"
startMenu="no"
desktop="yes"
startup="no"
encoding="UTF-8"
terminal="true"
type="Application" />
<!--
<shortcut name="Syndie documentation"
programGroup="yes"
desktop="yes"
applications="yes"
startMenu="yes"
startup="no"
target="$INSTALL_PATH/doc/web/index.html"
url="$INSTALL_PATH/doc/web/index.html"
type="Link"
encoding="UTF-8"
description="Syndie documentation index" />
-->
<shortcut name="Uninstall Syndie"
target="java"
commandLine="-jar &quot;$INSTALL_PATH/Uninstaller/uninstaller.jar&quot;"
workingDirectory=""
initialState="normal"
startMenu="no"
programGroup="yes"
startup="no"
encoding="UTF-8"
type="Application" />
</shortcuts>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<shortcuts>
<programGroup defaultName="Syndie" location="applications" />
<shortcut name="Syndie (text interface)"
programGroup="yes"
desktop="yes"
applications="no"
target="$INSTALL_PATH\bin\syndie.exe"
description="Main text interface to Syndie">
<createForPack name="Base" />
</shortcut>
<shortcut name="Syndie documentation"
programGroup="yes"
desktop="yes"
applications="no"
target="$INSTALL_PATH\doc\web\index.html"
description="Local Syndie documentation">
<createForPack name="Base" />
</shortcut>
<shortcut name="Uninstall Syndie"
programGroup="yes"
desktop="yes"
applications="no"
target="$INSTALL_PATH\uninstaller\uninstaller.jar"
description="No more Syndie">
<createForPack name="Base" />
</shortcut>
</shortcuts>

BIN
resources/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

BIN
resources/avatar2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

BIN
resources/icon1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

View File

@ -0,0 +1,59 @@
INTL.nym.NymTab.name=Nyms
INTL.nym.NymTab.groupMain=Select identity
INTL.nym.NymTab.nymLabel=You are currently operating as
INTL.nym.NymTab.pubKeyLabel=Your public key hash is a cryptographic permutation of your public key, which you can share with people for them to identify you without keeping track of some 256 bytes
INTL.nym.NymTab.pubKeyCopy=Copy to clipboard
INTL.nym.NymTab.privKeyLabel=Your private key is stored at a place we can't tell you about, because its so secret you just wouldn't believe us
INTL.nym.NymTab.privKeyRead=Import...
INTL.nym.NymTab.privKeySave=Export...
INTL.nym.NymTab.groupCreate=Create a new nym
INTL.nym.NymTab.createLabel=Local name
INTL.nym.NymTab.createButton=Create
INTL.nym.NymTab.keyLabel=Identity:
INTL.nym.NymTab.keyGenerate=Generate a new identity
INTL.nym.NymTab.keyImport=Import an existing identity
INTL.nym.NymTab.keyImportBrowse=Browse...
INTL.nym.BlogTab.name=Blogging
INTL.nym.BlogSearch.groupName=Blog search
INTL.nym.BlogSearch.text=Keyword
INTL.nym.BlogSearch.tag=Tag
INTL.nym.BlogSearch.date=Activity date
INTL.nym.BlogSearch.uri=URI
INTL.blog.BlogTab.name=Blogging
INTL.blog.BlogTree.myBlogs=My blogs
INTL.blog.BlogTree.watched=Watched blogs
INTL.blog.BlogTree.new=New blogs
INTL.blog.BlogTree.highlights=Highlights
INTL.blog.BlogTree.myBlogs.view=&View
INTL.blog.BlogTree.myBlogs.post=&Post
INTL.blog.BlogTree.myBlogs.manage=&Manage
INTL.blog.BlogTree.watched.pick=&Watch blogs...
INTL.blog.BlogTree.watched.view=&View
INTL.blog.BlogTree.watched.unwatch=&Unwatch
INTL.blog.BlogTree.watched.rename=&Rename
INTL.blog.BlogTree.new.markRead=Mark &read
INTL.blog.BlogTree.new.view=&View
INTL.blog.BlogTree.new.hide=&Hide
INTL.blog.BlogTree.new.ban=&Ban
INTL.blog.BlogTree.highlights.manage=&Manage highlighting...
INTL.blog.BlogTree.highlights.view=&View
INTL.blog.BlogTree.highlights.viewBlog=View &blog
INTL.blog.HighlightTab.name=Manage highlights
INTL.blog.HighlightTab.channelGroup=Posted in
INTL.blog.HighlightTab.channelAny=any blog
INTL.blog.HighlightTab.channelWatched=watched blogs
INTL.blog.HighlightTab.authorGroup=Posted by
INTL.blog.HighlightTab.authorAny=any author
INTL.blog.HighlightTab.authorSpecified=selected authors
INTL.blog.HighlightTab.tagGroup=Tagged with
INTL.blog.HighlightTab.tagAdd=Add
INTL.blog.HighlightTab.tagRemove=Remove
INTL.blog.HighlightTab.dateGroup=Active in the last
INTL.blog.HighlightTab.dateDays=days
INTL.blog.HighlightTab.actionGroup=Action
INTL.blog.HighlightTab.save=Save
INTL.blog.HighlightTab.cancel=Cancel

2
resources/syndie Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
java -cp $INSTALL_PATH/lib/syndie.jar:$INSTALL_PATH/lib/hsqldb.jar syndie.db.TextUI $@

1
resources/syndie.bat Executable file
View File

@ -0,0 +1 @@
java -cp "$INSTALL_PATH\lib\syndie.jar";"$INSTALL_PATH\lib\hsqldb.jar" syndie.db.TextUI %*

4
resources/welcome.txt Normal file
View File

@ -0,0 +1,4 @@
Congratulations for getting Syndie installed!
Documentation can be found in doc/web/syndie.html,
or online at http://syndie.i2p.net/

View File

@ -0,0 +1,39 @@
/*
* Created on Jul 17, 2004
*
*/
package freenet.support.CPUInformation;
/**
* @author Iakin
* An interface for classes that provide lowlevel information about AMD CPU's
*
* free (adj.): unencumbered; not under the control of others
* Written by Iakin in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
public interface AMDCPUInfo extends CPUInfo {
/**
* @return true iff the CPU present in the machine is at least an 'k6' CPU
*/
public boolean IsK6Compatible();
/**
* @return true iff the CPU present in the machine is at least an 'k6-2' CPU
*/
public boolean IsK6_2_Compatible();
/**
* @return true iff the CPU present in the machine is at least an 'k6-3' CPU
*/
public boolean IsK6_3_Compatible();
/**
* @return true iff the CPU present in the machine is at least an 'k7' CPU (Atlhon, Duron etc. and better)
*/
public boolean IsAthlonCompatible();
/**
* @return true iff the CPU present in the machine is at least an 'k8' CPU (Atlhon 64, Opteron etc. and better)
*/
public boolean IsAthlon64Compatible();
}

View File

@ -0,0 +1,573 @@
/*
* Created on Jul 14, 2004
*/
package freenet.support.CPUInformation;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
* @author Iakin
* A class for retrieveing details about the CPU using the CPUID assembly instruction.
* A good resource for information about the CPUID instruction can be found here:
* http://www.paradicesoftware.com/specs/cpuid/index.htm
*
* free (adj.): unencumbered; not under the control of others
* Written by Iakin in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
public class CPUID {
/** did we load the native lib correctly? */
private static boolean _nativeOk = false;
/**
* do we want to dump some basic success/failure info to stderr during
* initialization? this would otherwise use the Log component, but this makes
* it easier for other systems to reuse this class
*/
private static final boolean _doLog = System.getProperty("jcpuid.dontLog") == null;
//.matches() is a java 1.4+ addition, using a simplified version for 1.3+
//private static final boolean isX86 = System.getProperty("os.arch").toLowerCase().matches("i?[x0-9]86(_64)?");
private static final boolean isX86 = (-1 != System.getProperty("os.arch").indexOf("86"));
private static final String libPrefix = (System.getProperty("os.name").startsWith("Win") ? "" : "lib");
private static final String libSuffix = (System.getProperty("os.name").startsWith("Win") ? ".dll" : ".so");
static
{
loadNative();
}
//A class that can (amongst other things I assume) represent the state of the
//different CPU registers after a call to the CPUID assembly method
protected static class CPUIDResult {
final int EAX;
final int EBX;
final int ECX;
final int EDX;
CPUIDResult(int EAX,int EBX,int ECX, int EDX)
{
this.EAX = EAX;
this.EBX = EBX;
this.ECX = ECX;
this.EDX = EDX;
}
}
/**Calls the indicated CPUID function and returns the result of the execution
*
* @param iFunction The CPUID function to call, should be 0 or larger
* @return The contents of the CPU registers after the call to the CPUID function
*/
private static native CPUIDResult doCPUID(int iFunction);
private static String getCPUVendorID()
{
CPUIDResult c = doCPUID(0);
StringBuffer sb= new StringBuffer(13);
sb.append((char)( c.EBX & 0xFF));
sb.append((char)((c.EBX >> 8) & 0xFF));
sb.append((char)((c.EBX >> 16) & 0xFF));
sb.append((char)((c.EBX >> 24) & 0xFF));
sb.append((char)( c.EDX & 0xFF));
sb.append((char)((c.EDX >> 8) & 0xFF));
sb.append((char)((c.EDX >> 16) & 0xFF));
sb.append((char)((c.EDX >> 24) & 0xFF));
sb.append((char)( c.ECX & 0xFF));
sb.append((char)((c.ECX >> 8) & 0xFF));
sb.append((char)((c.ECX >> 16) & 0xFF));
sb.append((char)((c.ECX >> 24) & 0xFF));
return sb.toString();
}
private static int getCPUFamily()
{
CPUIDResult c = doCPUID(1);
return (c.EAX >> 8) & 0xf;
}
private static int getCPUModel()
{
CPUIDResult c = doCPUID(1);
return (c.EAX >> 4) & 0xf;
}
private static int getCPUExtendedModel()
{
CPUIDResult c = doCPUID(1);
return (c.EAX >> 16) & 0xf;
}
private static int getCPUType()
{
CPUIDResult c = doCPUID(1);
return (c.EAX >> 12) & 0xf;
}
private static int getCPUExtendedFamily()
{
CPUIDResult c = doCPUID(1);
return (c.EAX >> 20) & 0xff;
}
private static int getCPUStepping()
{
CPUIDResult c = doCPUID(1);
return c.EAX & 0xf;
}
private static int getCPUFlags()
{
CPUIDResult c = doCPUID(1);
return c.EDX;
}
//Returns a CPUInfo item for the current type of CPU
//If I could I would declare this method in a interface named
//CPUInfoProvider and implement that interface in this class.
//This would make it easier for other people to understand that there
//is nothing preventing them from coding up new providers, probably using
//other detection methods than the x86-only CPUID instruction
public static CPUInfo getInfo() throws UnknownCPUException
{
if(!_nativeOk)
throw new UnknownCPUException("Failed to read CPU information from the system. Please verify the existence of the jcpuid dll/so.");
if(getCPUVendorID().equals("CentaurHauls"))
return new VIAC3Impl();
if(!isX86)
throw new UnknownCPUException("Failed to read CPU information from the system. The CPUID instruction exists on x86 CPU's only");
if(getCPUVendorID().equals("AuthenticAMD"))
return new AMDInfoImpl();
if(getCPUVendorID().equals("GenuineIntel"))
return new IntelInfoImpl();
throw new UnknownCPUException("Unknown CPU type: '"+getCPUVendorID()+"'");
}
protected abstract static class CPUIDCPUInfo
{
public String getVendor()
{
return getCPUVendorID();
}
public boolean hasMMX(){
return (getCPUFlags() & 0x800000) >0; //Bit 23
}
public boolean hasSSE(){
return (getCPUFlags() & 0x2000000) >0; //Bit 25
}
public boolean hasSSE2(){
return (getCPUFlags() & 0x4000000) >0; //Bit 26
}
public boolean IsC3Compatible() { return false; }
}
protected static class VIAC3Impl extends CPUIDCPUInfo implements CPUInfo {
public boolean isC3Compatible() { return true; }
public String getCPUModelString() { return "VIA C3"; }
}
protected static class AMDInfoImpl extends CPUIDCPUInfo implements AMDCPUInfo
{
public boolean IsK6Compatible()
{
return getCPUFamily() >= 5 && getCPUModel() >= 6;
}
public boolean IsK6_2_Compatible()
{
return getCPUFamily() >= 5 && getCPUModel() >= 8;
}
public boolean IsK6_3_Compatible()
{
return getCPUFamily() >= 5 && getCPUModel() >= 9;
}
public boolean IsAthlonCompatible()
{
return getCPUFamily() >= 6;
}
public boolean IsAthlon64Compatible()
{
return getCPUFamily() == 15 && getCPUExtendedFamily() == 0;
}
public String getCPUModelString() throws UnknownCPUException
{
if(getCPUFamily() == 4){
switch(getCPUModel()){
case 3:
return "486 DX/2";
case 7:
return "486 DX/2-WB";
case 8:
return "486 DX/4";
case 9:
return "486 DX/4-WB";
case 14:
return "Am5x86-WT";
case 15:
return "Am5x86-WB";
}
}
if(getCPUFamily() == 5){
switch(getCPUModel()){
case 0:
return "K5/SSA5";
case 1:
return "K5";
case 2:
return "K5";
case 3:
return "K5";
case 6:
return "K6";
case 7:
return "K6";
case 8:
return "K6-2";
case 9:
return "K6-3";
case 13:
return "K6-2+ or K6-III+";
}
}
if(getCPUFamily() == 6){
switch(getCPUModel()){
case 0:
return "Athlon (250 nm)";
case 1:
return "Athlon (250 nm)";
case 2:
return "Athlon (180 nm)";
case 3:
return "Duron";
case 4:
return "Athlon (Thunderbird)";
case 6:
return "Athlon (Palamino)";
case 7:
return "Duron (Morgan)";
case 8:
return "Athlon (Thoroughbred)";
case 10:
return "Athlon (Barton)";
}
}
if(getCPUFamily() == 15){
if(getCPUExtendedFamily() == 0){
switch(getCPUModel()){
case 4:
return "Athlon 64";
case 5:
return "Athlon 64 FX Opteron";
case 12:
return "Athlon 64";
default: // is this safe?
return "Athlon 64 (unknown)";
}
}
}
throw new UnknownCPUException("Unknown AMD CPU; Family="+getCPUFamily()+", Model="+getCPUModel());
}
}
protected static class IntelInfoImpl extends CPUIDCPUInfo implements IntelCPUInfo
{
public boolean IsPentiumCompatible()
{
return getCPUFamily() >= 5;
}
public boolean IsPentiumMMXCompatible()
{
return IsPentium2Compatible() || (getCPUFamily() == 5 && (getCPUModel() ==4 || getCPUModel() == 8));
}
public boolean IsPentium2Compatible()
{
return getCPUFamily() > 6 || (getCPUFamily() == 6 && getCPUModel() >=3);
}
public boolean IsPentium3Compatible()
{
return getCPUFamily() > 6 || (getCPUFamily() == 6 && getCPUModel() >=7);
}
public boolean IsPentium4Compatible()
{
return getCPUFamily() >= 15;
}
public String getCPUModelString() throws UnknownCPUException {
if(getCPUFamily() == 4){
switch(getCPUModel()){
case 0:
return "486 DX-25/33";
case 1:
return "486 DX-50";
case 2:
return "486 SX";
case 3:
return "486 DX/2";
case 4:
return "486 SL";
case 5:
return "486 SX/2";
case 7:
return "486 DX/2-WB";
case 8:
return "486 DX/4";
case 9:
return "486 DX/4-WB";
}
}
if(getCPUFamily() == 5){
switch(getCPUModel()){
case 0:
return "Pentium 60/66 A-step";
case 1:
return "Pentium 60/66";
case 2:
return "Pentium 75 - 200";
case 3:
return "OverDrive PODP5V83";
case 4:
return "Pentium MMX";
case 7:
return "Mobile Pentium 75 - 200";
case 8:
return "Mobile Pentium MMX";
}
}
if(getCPUFamily() == 6){
switch(getCPUModel()){
case 0:
return "Pentium Pro A-step";
case 1:
return "Pentium Pro";
case 3:
return "Pentium II (Klamath)";
case 5:
return "Pentium II (Deschutes), Celeron (Covington), Mobile Pentium II (Dixon)";
case 6:
return "Mobile Pentium II, Celeron (Mendocino)";
case 7:
return "Pentium III (Katmai)";
case 8:
return "Pentium III (Coppermine), Celeron w/SSE";
case 9:
return "Mobile Pentium III";
case 10:
return "Pentium III Xeon (Cascades)";
case 11:
return "Pentium III (130 nm)";
}
}
if(getCPUFamily() == 7){
switch(getCPUModel()){
//Itanium.. TODO
}
}
if(getCPUFamily() == 15){
if(getCPUExtendedFamily() == 0){
switch(getCPUModel()){
case 0:
return "Pentium IV (180 nm)";
case 1:
return "Pentium IV (180 nm)";
case 2:
return "Pentium IV (130 nm)";
case 3:
return "Pentium IV (90 nm)";
}
}
if(getCPUExtendedFamily() == 1){
switch(getCPUModel()){
// Itanium 2.. TODO
}
}
}
throw new UnknownCPUException("Unknown Intel CPU; Family="+getCPUFamily()+", Model="+getCPUModel());
}
}
public static void main(String args[])
{
if(!_nativeOk){
System.out.println("**Failed to retrieve CPUInfo. Please verify the existence of jcpuid dll/so**");
}
System.out.println("**CPUInfo**");
System.out.println("CPU Vendor: " + getCPUVendorID());
System.out.println("CPU Family: " + getCPUFamily());
System.out.println("CPU Model: " + getCPUModel());
System.out.println("CPU Stepping: " + getCPUStepping());
System.out.println("CPU Flags: " + getCPUFlags());
CPUInfo c = getInfo();
System.out.println(" **More CPUInfo**");
System.out.println(" CPU model string: " + c.getCPUModelString());
System.out.println(" CPU has MMX: " + c.hasMMX());
System.out.println(" CPU has SSE: " + c.hasSSE());
System.out.println(" CPU has SSE2: " + c.hasSSE2());
if(c instanceof IntelCPUInfo){
System.out.println(" **Intel-info**");
System.out.println(" Is pII-compatible: "+((IntelCPUInfo)c).IsPentium2Compatible());
System.out.println(" Is pIII-compatible: "+((IntelCPUInfo)c).IsPentium3Compatible());
System.out.println(" Is pIV-compatible: "+((IntelCPUInfo)c).IsPentium4Compatible());
}
if(c instanceof AMDCPUInfo){
System.out.println(" **AMD-info**");
System.out.println(" Is Athlon-compatible: "+((AMDCPUInfo)c).IsAthlonCompatible());
}
}
/**
* <p>Do whatever we can to load up the native library.
* If it can find a custom built jcpuid.dll / libjcpuid.so, it'll use that. Otherwise
* it'll try to look in the classpath for the correct library (see loadFromResource).
* If the user specifies -Djcpuid.enable=false it'll skip all of this.</p>
*
*/
private static final void loadNative() {
try{
String wantedProp = System.getProperty("jcpuid.enable", "true");
boolean wantNative = "true".equalsIgnoreCase(wantedProp);
if (wantNative) {
boolean loaded = loadGeneric();
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Native CPUID library '"+getLibraryMiddlePart()+"' loaded from somewhere in the path");
} else {
loaded = loadFromResource();
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Native CPUID library '"+getResourceName()+"' loaded from resource");
} else {
_nativeOk = false;
if (_doLog)
System.err.println("WARN: Native CPUID library jcpuid not loaded - will not be able to read CPU information using CPUID");
}
}
} else {
if (_doLog)
System.err.println("INFO: Native CPUID library jcpuid not loaded - will not be able to read CPU information using CPUID");
}
}catch(Exception e){
if (_doLog)
System.err.println("INFO: Native CPUID library jcpuid not loaded, reason: '"+e.getMessage()+"' - will not be able to read CPU information using CPUID");
}
}
/**
* <p>Try loading it from an explictly built jcpuid.dll / libjcpuid.so</p>
*
* @return true if it was loaded successfully, else false
*
*/
private static final boolean loadGeneric() {
try {
System.loadLibrary("jcpuid");
return true;
} catch (UnsatisfiedLinkError ule) {
// fallthrough, try the OS-specific filename
}
try {
System.loadLibrary(getLibraryMiddlePart());
return true;
} catch (UnsatisfiedLinkError ule) {
return false;
}
}
/**
* <p>Check all of the jars in the classpath for the jcpuid dll/so.
* This file should be stored in the resource in the same package as this class.
*
* <p>This is a pretty ugly hack, using the general technique illustrated by the
* onion FEC libraries. It works by pulling the resource, writing out the
* byte stream to a temporary file, loading the native library from that file,
* then deleting the file.</p>
*
* @return true if it was loaded successfully, else false
*
*/
private static final boolean loadFromResource() {
String resourceName = getResourceName();
if (resourceName == null) return false;
URL resource = CPUID.class.getClassLoader().getResource(resourceName);
if (resource == null) {
if (_doLog)
System.err.println("ERROR: Resource name [" + resourceName + "] was not found");
return false;
}
File outFile = null;
FileOutputStream fos = null;
try {
InputStream libStream = resource.openStream();
outFile = new File(libPrefix + "jcpuid" + libSuffix);
fos = new FileOutputStream(outFile);
byte buf[] = new byte[4096*1024];
while (true) {
int read = libStream.read(buf);
if (read < 0) break;
fos.write(buf, 0, read);
}
fos.close();
fos = null;
System.load(outFile.getAbsolutePath());//System.load requires an absolute path to the lib
return true;
} catch (UnsatisfiedLinkError ule) {
if (_doLog) {
System.err.println("ERROR: The resource " + resourceName
+ " was not a valid library for this platform");
ule.printStackTrace();
}
return false;
} catch (IOException ioe) {
if (_doLog) {
System.err.println("ERROR: Problem writing out the temporary native library data");
ioe.printStackTrace();
}
return false;
} finally {
if (fos != null) {
try { fos.close(); } catch (IOException ioe) {}
}
}
}
private static final String getResourceName()
{
return getLibraryPrefix()+getLibraryMiddlePart()+"."+getLibrarySuffix();
}
private static final String getLibraryPrefix()
{
boolean isWindows =System.getProperty("os.name").toLowerCase().indexOf("windows") != -1;
if(isWindows)
return "";
else
return "lib";
}
private static final String getLibraryMiddlePart(){
boolean isWindows =(System.getProperty("os.name").toLowerCase().indexOf("windows") != -1);
boolean isLinux =(System.getProperty("os.name").toLowerCase().indexOf("linux") != -1);
boolean isFreebsd =(System.getProperty("os.name").toLowerCase().indexOf("freebsd") != -1);
if(isWindows)
return "jcpuid-x86-windows"; // The convention on Windows
if(isLinux)
return "jcpuid-x86-linux"; // The convention on linux...
if(isFreebsd)
return "jcpuid-x86-freebsd"; // The convention on freebsd...
throw new RuntimeException("Dont know jcpuid library name for os type '"+System.getProperty("os.name")+"'");
}
private static final String getLibrarySuffix()
{
boolean isWindows =System.getProperty("os.name").toLowerCase().indexOf("windows") != -1;
if(isWindows)
return "dll";
else
return "so";
}
}

View File

@ -0,0 +1,46 @@
/*
* Created on Jul 16, 2004
*
*/
package freenet.support.CPUInformation;
/**
* @author Iakin
* An interface for classes that provide lowlevel information about CPU's
*
* free (adj.): unencumbered; not under the control of others
* Written by Iakin in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
public interface CPUInfo
{
/**
* @return A string indicating the vendor of the CPU.
*/
public String getVendor();
/**
* @return A string detailing what type of CPU that is present in the machine. I.e. 'Pentium IV' etc.
* @throws UnknownCPUException If for any reson the retrieval of the requested information
* failed. The message encapsulated in the execption indicates the
* cause of the failure.
*/
public String getCPUModelString() throws UnknownCPUException;
/**
* @return true iff the CPU support the MMX instruction set.
*/
public boolean hasMMX();
/**
* @return true iff the CPU support the SSE instruction set.
*/
public boolean hasSSE();
/**
* @return true iff the CPU support the SSE2 instruction set.
*/
public boolean hasSSE2();
public boolean IsC3Compatible();
}

View File

@ -0,0 +1,40 @@
/*
* Created on Jul 17, 2004
*
*/
package freenet.support.CPUInformation;
/**
* @author Iakin
* An interface for classes that provide lowlevel information about Intel CPU's
*
* free (adj.): unencumbered; not under the control of others
* Written by Iakin in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
public interface IntelCPUInfo extends CPUInfo {
/**
* @return true iff the CPU is at least a Pentium CPU.
*/
public boolean IsPentiumCompatible();
/**
* @return true iff the CPU is at least a Pentium which implements the MMX instruction/feature set.
*/
public boolean IsPentiumMMXCompatible();
/**
* @return true iff the CPU implements at least the p6 instruction set (Pentium II or better).
* Please note that an PentimPro CPU causes/should cause this method to return false (due to that CPU using a
* very early implementation of the p6 instruction set. No MMX etc.)
*/
public boolean IsPentium2Compatible();
/**
* @return true iff the CPU implements at least a Pentium III level of the p6 instruction/feature set.
*/
public boolean IsPentium3Compatible();
/**
* @return true iff the CPU implements at least a Pentium IV level instruction/feature set.
*/
public boolean IsPentium4Compatible();
}

View File

@ -0,0 +1,18 @@
/*
* Created on Jul 16, 2004
*/
package freenet.support.CPUInformation;
/**
* @author Iakin
*
*/
public class UnknownCPUException extends RuntimeException {
public UnknownCPUException() {
super();
}
public UnknownCPUException(String message) {
super(message);
}
}

View File

@ -0,0 +1,198 @@
package gnu.crypto.hash;
// ----------------------------------------------------------------------------
// $Id: BaseHashStandalone.java,v 1.1 2006-07-04 16:17:55 jrandom Exp $
//
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto 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 2, or (at your option)
// any later version.
//
// GNU Crypto 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; see the file COPYING. If not, write to the
//
// Free Software Foundation Inc.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301
// USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library. Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module. An independent module is a module which is
// not derived from or based on this library. If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so. If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------
/**
* <p>A base abstract class to facilitate hash implementations.</p>
*
* @version $Revision: 1.1 $
*/
public abstract class BaseHashStandalone implements IMessageDigestStandalone {
// Constants and variables
// -------------------------------------------------------------------------
/** The canonical name prefix of the hash. */
protected String name;
/** The hash (output) size in bytes. */
protected int hashSize;
/** The hash (inner) block size in bytes. */
protected int blockSize;
/** Number of bytes processed so far. */
protected long count;
/** Temporary input buffer. */
protected byte[] buffer;
// Constructor(s)
// -------------------------------------------------------------------------
/**
* <p>Trivial constructor for use by concrete subclasses.</p>
*
* @param name the canonical name prefix of this instance.
* @param hashSize the block size of the output in bytes.
* @param blockSize the block size of the internal transform.
*/
protected BaseHashStandalone(String name, int hashSize, int blockSize) {
super();
this.name = name;
this.hashSize = hashSize;
this.blockSize = blockSize;
this.buffer = new byte[blockSize];
resetContext();
}
// Class methods
// -------------------------------------------------------------------------
// Instance methods
// -------------------------------------------------------------------------
// IMessageDigestStandalone interface implementation ---------------------------------
public String name() {
return name;
}
public int hashSize() {
return hashSize;
}
public int blockSize() {
return blockSize;
}
public void update(byte b) {
// compute number of bytes still unhashed; ie. present in buffer
int i = (int)(count % blockSize);
count++;
buffer[i] = b;
if (i == (blockSize - 1)) {
transform(buffer, 0);
}
}
public void update(byte[] b) {
update(b, 0, b.length);
}
public void update(byte[] b, int offset, int len) {
int n = (int)(count % blockSize);
count += len;
int partLen = blockSize - n;
int i = 0;
if (len >= partLen) {
System.arraycopy(b, offset, buffer, n, partLen);
transform(buffer, 0);
for (i = partLen; i + blockSize - 1 < len; i+= blockSize) {
transform(b, offset + i);
}
n = 0;
}
if (i < len) {
System.arraycopy(b, offset + i, buffer, n, len - i);
}
}
public byte[] digest() {
byte[] tail = padBuffer(); // pad remaining bytes in buffer
update(tail, 0, tail.length); // last transform of a message
byte[] result = getResult(); // make a result out of context
reset(); // reset this instance for future re-use
return result;
}
public void reset() { // reset this instance for future re-use
count = 0L;
for (int i = 0; i < blockSize; ) {
buffer[i++] = 0;
}
resetContext();
}
// methods to be implemented by concrete subclasses ------------------------
public abstract Object clone();
public abstract boolean selfTest();
/**
* <p>Returns the byte array to use as padding before completing a hash
* operation.</p>
*
* @return the bytes to pad the remaining bytes in the buffer before
* completing a hash operation.
*/
protected abstract byte[] padBuffer();
/**
* <p>Constructs the result from the contents of the current context.</p>
*
* @return the output of the completed hash operation.
*/
protected abstract byte[] getResult();
/** Resets the instance for future re-use. */
protected abstract void resetContext();
/**
* <p>The block digest transformation per se.</p>
*
* @param in the <i>blockSize</i> long block, as an array of bytes to digest.
* @param offset the index where the data to digest is located within the
* input buffer.
*/
protected abstract void transform(byte[] in, int offset);
}

View File

@ -0,0 +1,141 @@
package gnu.crypto.hash;
// ----------------------------------------------------------------------------
// $Id: IMessageDigestStandalone.java,v 1.1 2006-07-04 16:17:56 jrandom Exp $
//
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto 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 2, or (at your option)
// any later version.
//
// GNU Crypto 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; see the file COPYING. If not, write to the
//
// Free Software Foundation Inc.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301
// USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library. Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module. An independent module is a module which is
// not derived from or based on this library. If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so. If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------
/**
* <p>The basic visible methods of any hash algorithm.</p>
*
* <p>A hash (or message digest) algorithm produces its output by iterating a
* basic compression function on blocks of data.</p>
*
* @version $Revision: 1.1 $
*/
public interface IMessageDigestStandalone extends Cloneable {
// Constants
// -------------------------------------------------------------------------
// Methods
// -------------------------------------------------------------------------
/**
* <p>Returns the canonical name of this algorithm.</p>
*
* @return the canonical name of this instance.
*/
String name();
/**
* <p>Returns the output length in bytes of this message digest algorithm.</p>
*
* @return the output length in bytes of this message digest algorithm.
*/
int hashSize();
/**
* <p>Returns the algorithm's (inner) block size in bytes.</p>
*
* @return the algorithm's inner block size in bytes.
*/
int blockSize();
/**
* <p>Continues a message digest operation using the input byte.</p>
*
* @param b the input byte to digest.
*/
void update(byte b);
/**
* <p>Continues a message digest operation, by filling the buffer, processing
* data in the algorithm's HASH_SIZE-bit block(s), updating the context and
* count, and buffering the remaining bytes in buffer for the next
* operation.</p>
*
* @param in the input block.
*/
void update(byte[] in);
/**
* <p>Continues a message digest operation, by filling the buffer, processing
* data in the algorithm's HASH_SIZE-bit block(s), updating the context and
* count, and buffering the remaining bytes in buffer for the next
* operation.</p>
*
* @param in the input block.
* @param offset start of meaningful bytes in input block.
* @param length number of bytes, in input block, to consider.
*/
void update(byte[] in, int offset, int length);
/**
* <p>Completes the message digest by performing final operations such as
* padding and resetting the instance.</p>
*
* @return the array of bytes representing the hash value.
*/
byte[] digest();
/**
* <p>Resets the current context of this instance clearing any eventually cached
* intermediary values.</p>
*/
void reset();
/**
* <p>A basic test. Ensures that the digest of a pre-determined message is equal
* to a known pre-computed value.</p>
*
* @return <tt>true</tt> if the implementation passes a basic self-test.
* Returns <tt>false</tt> otherwise.
*/
boolean selfTest();
/**
* <p>Returns a clone copy of this instance.</p>
*
* @return a clone copy of this instance.
*/
Object clone();
}

View File

@ -0,0 +1,276 @@
package gnu.crypto.hash;
// ----------------------------------------------------------------------------
// $Id: Sha256Standalone.java,v 1.3 2006-07-04 16:17:56 jrandom Exp $
//
// Copyright (C) 2003 Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto 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 2, or (at your option)
// any later version.
//
// GNU Crypto 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; see the file COPYING. If not, write to the
//
// Free Software Foundation Inc.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301
// USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library. Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module. An independent module is a module which is
// not derived from or based on this library. If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so. If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------
//import gnu.crypto.util.Util;
/**
* <p>Implementation of SHA2-1 [SHA-256] per the IETF Draft Specification.</p>
*
* <p>References:</p>
* <ol>
* <li><a href="http://ftp.ipv4.heanet.ie/pub/ietf/internet-drafts/draft-ietf-ipsec-ciph-aes-cbc-03.txt">
* Descriptions of SHA-256, SHA-384, and SHA-512</a>,</li>
* <li>http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf</li>
* </ol>
*
* Modified by jrandom@i2p.net to remove unnecessary gnu-crypto dependencies, and
* renamed from Sha256 to avoid conflicts with JVMs using gnu-crypto as their JCE
* provider.
*
* @version $Revision: 1.3 $
*/
public class Sha256Standalone extends BaseHashStandalone {
// Constants and variables
// -------------------------------------------------------------------------
private static final int[] k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
private static final int BLOCK_SIZE = 64; // inner block size in bytes
private static final String DIGEST0 =
"BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD";
private static final int[] w = new int[64];
/** caches the result of the correctness test, once executed. */
private static Boolean valid;
/** 256-bit interim result. */
private int h0, h1, h2, h3, h4, h5, h6, h7;
// Constructor(s)
// -------------------------------------------------------------------------
/** Trivial 0-arguments constructor. */
public Sha256Standalone() {
super("sha256/standalone", 32, BLOCK_SIZE);
}
/**
* <p>Private constructor for cloning purposes.</p>
*
* @param md the instance to clone.
*/
private Sha256Standalone(Sha256Standalone md) {
this();
this.h0 = md.h0;
this.h1 = md.h1;
this.h2 = md.h2;
this.h3 = md.h3;
this.h4 = md.h4;
this.h5 = md.h5;
this.h6 = md.h6;
this.h7 = md.h7;
this.count = md.count;
this.buffer = (byte[]) md.buffer.clone();
}
// Class methods
// -------------------------------------------------------------------------
/*
public static final int[] G(int hh0, int hh1, int hh2, int hh3, int hh4,
int hh5, int hh6, int hh7, byte[] in, int offset) {
return sha(hh0, hh1, hh2, hh3, hh4, hh5, hh6, hh7, in, offset);
}
*/
// Instance methods
// -------------------------------------------------------------------------
// java.lang.Cloneable interface implementation ----------------------------
public Object clone() {
return new Sha256Standalone(this);
}
// Implementation of concrete methods in BaseHashStandalone --------------------------
private int transformResult[] = new int[8];
protected void transform(byte[] in, int offset) {
//int[] result = sha(h0, h1, h2, h3, h4, h5, h6, h7, in, offset);
sha(h0, h1, h2, h3, h4, h5, h6, h7, in, offset, transformResult);
h0 = transformResult[0];
h1 = transformResult[1];
h2 = transformResult[2];
h3 = transformResult[3];
h4 = transformResult[4];
h5 = transformResult[5];
h6 = transformResult[6];
h7 = transformResult[7];
}
protected byte[] padBuffer() {
int n = (int) (count % BLOCK_SIZE);
int padding = (n < 56) ? (56 - n) : (120 - n);
byte[] result = new byte[padding + 8];
// padding is always binary 1 followed by binary 0s
result[0] = (byte) 0x80;
// save number of bits, casting the long to an array of 8 bytes
long bits = count << 3;
result[padding++] = (byte)(bits >>> 56);
result[padding++] = (byte)(bits >>> 48);
result[padding++] = (byte)(bits >>> 40);
result[padding++] = (byte)(bits >>> 32);
result[padding++] = (byte)(bits >>> 24);
result[padding++] = (byte)(bits >>> 16);
result[padding++] = (byte)(bits >>> 8);
result[padding ] = (byte) bits;
return result;
}
protected byte[] getResult() {
return new byte[] {
(byte)(h0 >>> 24), (byte)(h0 >>> 16), (byte)(h0 >>> 8), (byte) h0,
(byte)(h1 >>> 24), (byte)(h1 >>> 16), (byte)(h1 >>> 8), (byte) h1,
(byte)(h2 >>> 24), (byte)(h2 >>> 16), (byte)(h2 >>> 8), (byte) h2,
(byte)(h3 >>> 24), (byte)(h3 >>> 16), (byte)(h3 >>> 8), (byte) h3,
(byte)(h4 >>> 24), (byte)(h4 >>> 16), (byte)(h4 >>> 8), (byte) h4,
(byte)(h5 >>> 24), (byte)(h5 >>> 16), (byte)(h5 >>> 8), (byte) h5,
(byte)(h6 >>> 24), (byte)(h6 >>> 16), (byte)(h6 >>> 8), (byte) h6,
(byte)(h7 >>> 24), (byte)(h7 >>> 16), (byte)(h7 >>> 8), (byte) h7
};
}
protected void resetContext() {
// magic SHA-256 initialisation constants
h0 = 0x6a09e667;
h1 = 0xbb67ae85;
h2 = 0x3c6ef372;
h3 = 0xa54ff53a;
h4 = 0x510e527f;
h5 = 0x9b05688c;
h6 = 0x1f83d9ab;
h7 = 0x5be0cd19;
}
public boolean selfTest() {
if (valid == null) {
Sha256Standalone md = new Sha256Standalone();
md.update((byte) 0x61); // a
md.update((byte) 0x62); // b
md.update((byte) 0x63); // c
String result = "broken"; //Util.toString(md.digest());
valid = new Boolean(DIGEST0.equals(result));
}
return valid.booleanValue();
}
// SHA specific methods ----------------------------------------------------
private static final synchronized void
sha(int hh0, int hh1, int hh2, int hh3, int hh4, int hh5, int hh6, int hh7, byte[] in, int offset, int out[]) {
int A = hh0;
int B = hh1;
int C = hh2;
int D = hh3;
int E = hh4;
int F = hh5;
int G = hh6;
int H = hh7;
int r, T, T2;
for (r = 0; r < 16; r++) {
w[r] = in[offset++] << 24 |
(in[offset++] & 0xFF) << 16 |
(in[offset++] & 0xFF) << 8 |
(in[offset++] & 0xFF);
}
for (r = 16; r < 64; r++) {
T = w[r - 2];
T2 = w[r - 15];
w[r] = (((T >>> 17) | (T << 15)) ^ ((T >>> 19) | (T << 13)) ^ (T >>> 10)) + w[r - 7] + (((T2 >>> 7) | (T2 << 25)) ^ ((T2 >>> 18) | (T2 << 14)) ^ (T2 >>> 3)) + w[r - 16];
}
for (r = 0; r < 64; r++) {
T = H + (((E >>> 6) | (E << 26)) ^ ((E >>> 11) | (E << 21)) ^ ((E >>> 25) | (E << 7))) + ((E & F) ^ (~E & G)) + k[r] + w[r];
T2 = (((A >>> 2) | (A << 30)) ^ ((A >>> 13) | (A << 19)) ^ ((A >>> 22) | (A << 10))) + ((A & B) ^ (A & C) ^ (B & C));
H = G;
G = F;
F = E;
E = D + T;
D = C;
C = B;
B = A;
A = T + T2;
}
/*
return new int[] {
hh0 + A, hh1 + B, hh2 + C, hh3 + D, hh4 + E, hh5 + F, hh6 + G, hh7 + H
};
*/
out[0] = hh0 + A;
out[1] = hh1 + B;
out[2] = hh2 + C;
out[3] = hh3 + D;
out[4] = hh4 + E;
out[5] = hh5 + F;
out[6] = hh6 + G;
out[7] = hh7 + H;
}
}

View File

@ -0,0 +1,172 @@
package gnu.crypto.prng;
import java.util.*;
/**
* fortuna instance that tries to avoid blocking if at all possible by using separate
* filled buffer segments rather than one buffer (and blocking when that buffer's data
* has been eaten)
*/
public class AsyncFortunaStandalone extends FortunaStandalone implements Runnable {
private static final int BUFFERS = 16;
private static final int BUFSIZE = 256*1024;
private final byte asyncBuffers[][] = new byte[BUFFERS][BUFSIZE];
private final int status[] = new int[BUFFERS];
private int nextBuf = 0;
private static final int STATUS_NEED_FILL = 0;
private static final int STATUS_FILLING = 1;
private static final int STATUS_FILLED = 2;
private static final int STATUS_LIVE = 3;
public AsyncFortunaStandalone() {
super();
for (int i = 0; i < BUFFERS; i++)
status[i] = STATUS_NEED_FILL;
}
public void startup() {
Thread refillThread = new Thread(this, "PRNG");
refillThread.setDaemon(true);
refillThread.setPriority(Thread.MIN_PRIORITY+1);
refillThread.start();
}
/** the seed is only propogated once the prng is started with startup() */
public void seed(byte val[]) {
Map props = new HashMap(1);
props.put(SEED, (Object)val);
init(props);
//fillBlock();
}
protected void allocBuffer() {}
/**
* make the next available filled buffer current, scheduling any unfilled
* buffers for refill, and blocking until at least one buffer is ready
*/
protected void rotateBuffer() {
synchronized (asyncBuffers) {
// wait until we get some filled
long before = System.currentTimeMillis();
long waited = 0;
while (status[nextBuf] != STATUS_FILLED) {
//System.out.println(Thread.currentThread().getName() + ": Next PRNG buffer "
// + nextBuf + " isn't ready (" + status[nextBuf] + ")");
//new Exception("source").printStackTrace();
asyncBuffers.notifyAll();
try {
asyncBuffers.wait();
} catch (InterruptedException ie) {}
waited = System.currentTimeMillis()-before;
}
//if (waited > 0)
//System.out.println(Thread.currentThread().getName() + ": Took " + waited
// + "ms for a full PRNG buffer to be found");
//System.out.println(Thread.currentThread().getName() + ": Switching to prng buffer " + nextBuf);
buffer = asyncBuffers[nextBuf];
status[nextBuf] = STATUS_LIVE;
int prev=nextBuf-1;
if (prev<0)
prev = BUFFERS-1;
if (status[prev] == STATUS_LIVE)
status[prev] = STATUS_NEED_FILL;
nextBuf++;
if (nextBuf >= BUFFERS)
nextBuf = 0;
asyncBuffers.notify();
}
}
public void run() {
while (true) {
int toFill = -1;
try {
synchronized (asyncBuffers) {
for (int i = 0; i < BUFFERS; i++) {
if (status[i] == STATUS_NEED_FILL) {
status[i] = STATUS_FILLING;
toFill = i;
break;
}
}
if (toFill == -1) {
//System.out.println(Thread.currentThread().getName() + ": All pending buffers full");
asyncBuffers.wait();
}
}
} catch (InterruptedException ie) {}
if (toFill != -1) {
//System.out.println(Thread.currentThread().getName() + ": Filling prng buffer " + toFill);
long before = System.currentTimeMillis();
doFill(asyncBuffers[toFill]);
long after = System.currentTimeMillis();
synchronized (asyncBuffers) {
status[toFill] = STATUS_FILLED;
//System.out.println(Thread.currentThread().getName() + ": Prng buffer " + toFill + " filled after " + (after-before));
asyncBuffers.notifyAll();
}
Thread.yield();
try { Thread.sleep((after-before)*5); } catch (InterruptedException ie) {}
}
}
}
public void fillBlock()
{
rotateBuffer();
}
private void doFill(byte buf[]) {
long start = System.currentTimeMillis();
if (pool0Count >= MIN_POOL_SIZE
&& System.currentTimeMillis() - lastReseed > 100)
{
reseedCount++;
//byte[] seed = new byte[0];
for (int i = 0; i < NUM_POOLS; i++)
{
if (reseedCount % (1 << i) == 0) {
generator.addRandomBytes(pools[i].digest());
}
}
lastReseed = System.currentTimeMillis();
}
generator.nextBytes(buf);
long now = System.currentTimeMillis();
long diff = now-lastRefill;
lastRefill = now;
long refillTime = now-start;
//System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
}
public static void main(String args[]) {
try {
AsyncFortunaStandalone rand = new AsyncFortunaStandalone();
byte seed[] = new byte[1024];
rand.seed(seed);
System.out.println("Before starting prng");
rand.startup();
System.out.println("Starting prng, waiting 1 minute");
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
System.out.println("PRNG started, beginning test");
long before = System.currentTimeMillis();
byte buf[] = new byte[1024];
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
for (int i = 0; i < 1024; i++) {
rand.nextBytes(buf);
gos.write(buf);
}
long after = System.currentTimeMillis();
gos.finish();
byte compressed[] = baos.toByteArray();
System.out.println("Compressed size of 1MB: " + compressed.length + " took " + (after-before));
} catch (Exception e) { e.printStackTrace(); }
try { Thread.sleep(5*60*1000); } catch (InterruptedException ie) {}
}
}

View File

@ -0,0 +1,183 @@
package gnu.crypto.prng;
// ----------------------------------------------------------------------------
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto 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 2, or (at your option)
// any later version.
//
// GNU Crypto 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; see the file COPYING. If not, write to the
//
// Free Software Foundation Inc.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301
// USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library. Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module. An independent module is a module which is
// not derived from or based on this library. If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so. If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------
import java.util.Map;
/**
* <p>An abstract class to facilitate implementing PRNG algorithms.</p>
*
* Modified slightly by jrandom for I2P (removing unneeded exceptions)
* @version $Revision: 1.2 $
*/
public abstract class BasePRNGStandalone implements IRandomStandalone {
// Constants and variables
// -------------------------------------------------------------------------
/** The canonical name prefix of the PRNG algorithm. */
protected String name;
/** Indicate if this instance has already been initialised or not. */
protected boolean initialised;
/** A temporary buffer to serve random bytes. */
protected byte[] buffer;
/** The index into buffer of where the next byte will come from. */
protected int ndx;
// Constructor(s)
// -------------------------------------------------------------------------
/**
* <p>Trivial constructor for use by concrete subclasses.</p>
*
* @param name the canonical name of this instance.
*/
protected BasePRNGStandalone(String name) {
super();
this.name = name;
initialised = false;
buffer = new byte[0];
}
// Class methods
// -------------------------------------------------------------------------
// Instance methods
// -------------------------------------------------------------------------
// IRandomStandalone interface implementation ----------------------------------------
public String name() {
return name;
}
public void init(Map attributes) {
this.setup(attributes);
ndx = 0;
initialised = true;
}
public byte nextByte() throws IllegalStateException {//, LimitReachedException {
if (!initialised) {
throw new IllegalStateException();
}
return nextByteInternal();
}
public void nextBytes(byte[] out) throws IllegalStateException {//, LimitReachedException {
nextBytes(out, 0, out.length);
}
public void nextBytes(byte[] out, int offset, int length)
throws IllegalStateException //, LimitReachedException
{
if (!initialised)
throw new IllegalStateException("not initialized");
if (length == 0)
return;
if (offset < 0 || length < 0 || offset + length > out.length)
throw new ArrayIndexOutOfBoundsException("offset=" + offset + " length="
+ length + " limit=" + out.length);
if (ndx >= buffer.length) {
fillBlock();
ndx = 0;
}
int count = 0;
while (count < length) {
int amount = Math.min(buffer.length - ndx, length - count);
System.arraycopy(buffer, ndx, out, offset+count, amount);
count += amount;
ndx += amount;
if (ndx >= buffer.length) {
fillBlock();
ndx = 0;
}
}
}
public void addRandomByte(byte b) {
throw new UnsupportedOperationException("random state is non-modifiable");
}
public void addRandomBytes(byte[] buffer) {
addRandomBytes(buffer, 0, buffer.length);
}
public void addRandomBytes(byte[] buffer, int offset, int length) {
throw new UnsupportedOperationException("random state is non-modifiable");
}
// Instance methods
// -------------------------------------------------------------------------
public boolean isInitialised() {
return initialised;
}
private byte nextByteInternal() {//throws LimitReachedException {
if (ndx >= buffer.length) {
this.fillBlock();
ndx = 0;
}
return buffer[ndx++];
}
// abstract methods to implement by subclasses -----------------------------
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
public abstract void setup(Map attributes);
public abstract void fillBlock(); //throws LimitReachedException;
}

View File

@ -0,0 +1,395 @@
/* Fortuna.java -- The Fortuna PRNG.
Copyright (C) 2004 Free Software Foundation, Inc.
This file is part of GNU Crypto.
GNU Crypto 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 2, or (at your option) any
later version.
GNU Crypto 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; see the file COPYING. If not, write to the
Free Software Foundation Inc.,
51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301
USA
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package gnu.crypto.prng;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.InvalidKeyException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import gnu.crypto.hash.Sha256Standalone;
import net.i2p.crypto.CryptixRijndael_Algorithm;
import net.i2p.crypto.CryptixAESKeyCache;
/**
* The Fortuna continuously-seeded pseudo-random number generator. This
* generator is composed of two major pieces: the entropy accumulator
* and the generator function. The former takes in random bits and
* incorporates them into the generator's state. The latter takes this
* base entropy and generates pseudo-random bits from it.
*
* <p>There are some things users of this class <em>must</em> be aware of:
*
* <dl>
* <dt>Adding Random Data</dt>
* <dd>This class does not do any polling of random sources, but rather
* provides an interface for adding random events. Applications that use
* this code <em>must</em> provide this mechanism. We use this design
* because an application writer who knows the system he is targeting
* is in a better position to judge what random data is available.</dd>
*
* <dt>Storing the Seed</dt>
* <dd>This class implements {@link Serializable} in such a way that it
* writes a 64 byte seed to the stream, and reads it back again when being
* deserialized. This is the extent of seed file management, however, and
* those using this class are encouraged to think deeply about when, how
* often, and where to store the seed.</dd>
* </dl>
*
* <p><b>References:</b></p>
*
* <ul>
* <li>Niels Ferguson and Bruce Schneier, <i>Practical Cryptography</i>,
* pp. 155--184. Wiley Publishing, Indianapolis. (2003 Niels Ferguson and
* Bruce Schneier). ISBN 0-471-22357-3.</li>
* </ul>
*
* Modified by jrandom for I2P to use a standalone gnu-crypto SHA256, Cryptix's AES,
* to strip out some unnecessary dependencies and increase the buffer size.
* Renamed from Fortuna to FortunaStandalone so it doesn't conflict with the
* gnu-crypto implementation, which has been imported into GNU/classpath
*
*/
public class FortunaStandalone extends BasePRNGStandalone implements Serializable, RandomEventListenerStandalone
{
private static final long serialVersionUID = 0xFACADE;
private static final int SEED_FILE_SIZE = 64;
static final int NUM_POOLS = 32;
static final int MIN_POOL_SIZE = 64;
final Generator generator;
final Sha256Standalone[] pools;
long lastReseed;
int pool;
int pool0Count;
int reseedCount;
static long refillCount = 0;
static long lastRefill = System.currentTimeMillis();
public static final String SEED = "gnu.crypto.prng.fortuna.seed";
public FortunaStandalone()
{
super("Fortuna i2p");
generator = new Generator();
pools = new Sha256Standalone[NUM_POOLS];
for (int i = 0; i < NUM_POOLS; i++)
pools[i] = new Sha256Standalone();
lastReseed = 0;
pool = 0;
pool0Count = 0;
allocBuffer();
}
protected void allocBuffer() {
buffer = new byte[4*1024*1024]; //256]; // larger buffer to reduce churn
}
public void seed(byte val[]) {
Map props = new HashMap(1);
props.put(SEED, (Object)val);
init(props);
fillBlock();
}
public void setup(Map attributes)
{
lastReseed = 0;
reseedCount = 0;
pool = 0;
pool0Count = 0;
generator.init(attributes);
}
public void fillBlock()
{
long start = System.currentTimeMillis();
if (pool0Count >= MIN_POOL_SIZE
&& System.currentTimeMillis() - lastReseed > 100)
{
reseedCount++;
//byte[] seed = new byte[0];
for (int i = 0; i < NUM_POOLS; i++)
{
if (reseedCount % (1 << i) == 0) {
generator.addRandomBytes(pools[i].digest());
}
}
lastReseed = System.currentTimeMillis();
}
generator.nextBytes(buffer);
long now = System.currentTimeMillis();
long diff = now-lastRefill;
lastRefill = now;
long refillTime = now-start;
System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
}
public void addRandomByte(byte b)
{
pools[pool].update(b);
if (pool == 0)
pool0Count++;
pool = (pool + 1) % NUM_POOLS;
}
public void addRandomBytes(byte[] buf, int offset, int length)
{
pools[pool].update(buf, offset, length);
if (pool == 0)
pool0Count += length;
pool = (pool + 1) % NUM_POOLS;
}
public void addRandomEvent(RandomEventStandalone event)
{
if (event.getPoolNumber() < 0 || event.getPoolNumber() >= pools.length)
throw new IllegalArgumentException("pool number out of range: "
+ event.getPoolNumber());
pools[event.getPoolNumber()].update(event.getSourceNumber());
pools[event.getPoolNumber()].update((byte) event.getData().length);
byte data[] = event.getData();
pools[event.getPoolNumber()].update(data, 0, data.length); //event.getData());
if (event.getPoolNumber() == 0)
pool0Count += event.getData().length;
}
// Reading and writing this object is equivalent to storing and retrieving
// the seed.
private void writeObject(ObjectOutputStream out) throws IOException
{
byte[] seed = new byte[SEED_FILE_SIZE];
generator.nextBytes(seed);
out.write(seed);
}
private void readObject(ObjectInputStream in) throws IOException
{
byte[] seed = new byte[SEED_FILE_SIZE];
in.readFully(seed);
generator.addRandomBytes(seed);
}
/**
* The Fortuna generator function. The generator is a PRNG in its own
* right; Fortuna itself is basically a wrapper around this generator
* that manages reseeding in a secure way.
*/
public static class Generator extends BasePRNGStandalone implements Cloneable
{
private static final int LIMIT = 1 << 20;
private final Sha256Standalone hash;
private final byte[] counter;
private final byte[] key;
/** current encryption key built from the keying material */
private Object cryptixKey;
private CryptixAESKeyCache.KeyCacheEntry cryptixKeyBuf;
private boolean seeded;
public Generator ()
{
super("Fortuna.generator.i2p");
this.hash = new Sha256Standalone();
counter = new byte[16]; //cipher.defaultBlockSize()];
buffer = new byte[16]; //cipher.defaultBlockSize()];
int keysize = 32;
key = new byte[keysize];
cryptixKeyBuf = CryptixAESKeyCache.createNew();
}
public final byte nextByte()
{
byte[] b = new byte[1];
nextBytes(b, 0, 1);
return b[0];
}
public final void nextBytes(byte[] out, int offset, int length)
{
if (!seeded)
throw new IllegalStateException("generator not seeded");
int count = 0;
do
{
int amount = Math.min(LIMIT, length - count);
super.nextBytes(out, offset+count, amount);
count += amount;
for (int i = 0; i < key.length; i += counter.length)
{
//fillBlock(); // inlined
CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
incrementCounter();
int l = Math.min(key.length - i, 16);//cipher.currentBlockSize());
System.arraycopy(buffer, 0, key, i, l);
}
resetKey();
}
while (count < length);
//fillBlock(); // inlined
CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
incrementCounter();
ndx = 0;
}
public final void addRandomByte(byte b)
{
addRandomBytes(new byte[] { b });
}
public final void addRandomBytes(byte[] seed, int offset, int length)
{
hash.update(key, 0, key.length);
hash.update(seed, offset, length);
byte[] newkey = hash.digest();
System.arraycopy(newkey, 0, key, 0, Math.min(key.length, newkey.length));
//hash.doFinal(key, 0);
resetKey();
incrementCounter();
seeded = true;
}
public final void fillBlock()
{
////i2p: this is not being checked as a microoptimization
//if (!seeded)
// throw new IllegalStateException("generator not seeded");
CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
incrementCounter();
}
public void setup(Map attributes)
{
seeded = false;
Arrays.fill(key, (byte) 0);
Arrays.fill(counter, (byte) 0);
byte[] seed = (byte[]) attributes.get(SEED);
if (seed != null)
addRandomBytes(seed);
}
/**
* Resets the cipher's key. This is done after every reseed, which
* combines the old key and the seed, and processes that throigh the
* hash function.
*/
private final void resetKey()
{
try {
cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, cryptixKeyBuf);
} catch (InvalidKeyException ike) {
throw new Error("hrmf", ike);
}
}
/**
* Increment `counter' as a sixteen-byte little-endian unsigned integer
* by one.
*/
private final void incrementCounter()
{
for (int i = 0; i < counter.length; i++)
{
counter[i]++;
if (counter[i] != 0)
break;
}
}
}
public static void main(String args[]) {
byte in[] = new byte[16];
byte out[] = new byte[16];
byte key[] = new byte[32];
try {
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
long beforeAll = System.currentTimeMillis();
for (int i = 0; i < 256; i++) {
//long before =System.currentTimeMillis();
for (int j = 0; j < 1024; j++)
CryptixRijndael_Algorithm.blockEncrypt(in, out, 0, 0, cryptixKey);
//long after = System.currentTimeMillis();
//System.out.println("encrypting 16KB took " + (after-before));
}
long after = System.currentTimeMillis();
System.out.println("encrypting 4MB took " + (after-beforeAll));
} catch (Exception e) { e.printStackTrace(); }
try {
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
byte data[] = new byte[4*1024*1024];
long beforeAll = System.currentTimeMillis();
//CryptixRijndael_Algorithm.ecbBulkEncrypt(data, data, cryptixKey);
long after = System.currentTimeMillis();
System.out.println("encrypting 4MB took " + (after-beforeAll));
} catch (Exception e) { e.printStackTrace(); }
/*
FortunaStandalone f = new FortunaStandalone();
java.util.HashMap props = new java.util.HashMap();
byte initSeed[] = new byte[1234];
new java.util.Random().nextBytes(initSeed);
long before = System.currentTimeMillis();
props.put(SEED, (byte[])initSeed);
f.init(props);
byte buf[] = new byte[8*1024];
for (int i = 0; i < 64*1024; i++) {
f.nextBytes(buf);
}
long time = System.currentTimeMillis() - before;
System.out.println("512MB took " + time + ", or " + (8*64d)/((double)time/1000d) +"MBps");
*/
}
}

View File

@ -0,0 +1,186 @@
package gnu.crypto.prng;
// ----------------------------------------------------------------------------
// $Id: IRandomStandalone.java,v 1.1 2006-07-04 16:18:04 jrandom Exp $
//
// Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto 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 2, or (at your option)
// any later version.
//
// GNU Crypto 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; see the file COPYING. If not, write to the
//
// Free Software Foundation Inc.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301
// USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library. Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module. An independent module is a module which is
// not derived from or based on this library. If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so. If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------
import java.util.Map;
/**
* <p>The basic visible methods of any pseudo-random number generator.</p>
*
* <p>The [HAC] defines a PRNG (as implemented in this library) as follows:</p>
*
* <ul>
* <li>"5.6 Definition: A pseudorandom bit generator (PRBG) is said to pass
* the <em>next-bit test</em> if there is no polynomial-time algorithm which,
* on input of the first <code>L</code> bits of an output sequence <code>S</code>,
* can predict the <code>(L+1)</code>st bit of <code>S</code> with a
* probability significantly grater than <code>1/2</code>."</li>
*
* <li>"5.8 Definition: A PRBG that passes the <em>next-bit test</em>
* (possibly under some plausible but unproved mathematical assumption such
* as the intractability of factoring integers) is called a
* <em>cryptographically secure pseudorandom bit generator</em> (CSPRBG)."</li>
* </ul>
*
* <p><b>IMPLEMENTATION NOTE</b>: Although all the concrete classes in this
* package implement the {@link Cloneable} interface, it is important to note
* here that such an operation, for those algorithms that use an underlting
* symmetric key block cipher, <b>DOES NOT</b> clone any session key material
* that may have been used in initialising the source PRNG (the instance to be
* cloned). Instead a clone of an already initialised PRNG, that uses and
* underlying symmetric key block cipher, is another instance with a clone of
* the same cipher that operates with the <b>same block size</b> but without any
* knowledge of neither key material nor key size.</p>
*
* <p>References:</p>
*
* <ol>
* <li><a href="http://www.cacr.math.uwaterloo.ca/hac">[HAC]</a>: Handbook of
* Applied Cryptography.<br>
* CRC Press, Inc. ISBN 0-8493-8523-7, 1997<br>
* Menezes, A., van Oorschot, P. and S. Vanstone.</li>
* </ol>
*
* @version $Revision: 1.1 $
*/
public interface IRandomStandalone extends Cloneable {
// Constants
// -------------------------------------------------------------------------
// Methods
// -------------------------------------------------------------------------
/**
* <p>Returns the canonical name of this instance.</p>
*
* @return the canonical name of this instance. */
String name();
/**
* <p>Initialises the pseudo-random number generator scheme with the
* appropriate attributes.</p>
*
* @param attributes a set of name-value pairs that describe the desired
* future instance behaviour.
* @exception IllegalArgumentException if at least one of the defined name/
* value pairs contains invalid data.
*/
void init(Map attributes);
/**
* <p>Returns the next 8 bits of random data generated from this instance.</p>
*
* @return the next 8 bits of random data generated from this instance.
* @exception IllegalStateException if the instance is not yet initialised.
* @exception LimLimitReachedExceptionStandalone this instance has reached its
* theoretical limit for generating non-repetitive pseudo-random data.
*/
byte nextByte() throws IllegalStateException, LimitReachedExceptionStandalone;
/**
* <p>Fills the designated byte array, starting from byte at index
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
* output of this generator instance.
*
* @param out the placeholder to contain the generated random bytes.
* @param offset the starting index in <i>out</i> to consider. This method
* does nothing if this parameter is not within <code>0</code> and
* <code>out.length</code>.
* @param length the maximum number of required random bytes. This method
* does nothing if this parameter is less than <code>1</code>.
* @exception IllegalStateException if the instance is not yet initialised.
* @exception LimitLimitReachedExceptionStandalonehis instance has reached its
* theoretical limit for generating non-repetitive pseudo-random data.
*/
void nextBytes(byte[] out, int offset, int length)
throws IllegalStateException, LimitReachedExceptionStandalone;
/**
* <p>Supplement, or possibly replace, the random state of this PRNG with
* a random byte.</p>
*
* <p>Implementations are not required to implement this method in any
* meaningful way; this may be a no-operation, and implementations may
* throw an {@link UnsupportedOperationException}.</p>
*
* @param b The byte to add.
*/
void addRandomByte(byte b);
/**
* <p>Supplement, or possibly replace, the random state of this PRNG with
* a sequence of new random bytes.</p>
*
* <p>Implementations are not required to implement this method in any
* meaningful way; this may be a no-operation, and implementations may
* throw an {@link UnsupportedOperationException}.</p>
*
* @param in The buffer of new random bytes to add.
*/
void addRandomBytes(byte[] in);
/**
* <p>Supplement, or possibly replace, the random state of this PRNG with
* a sequence of new random bytes.</p>
*
* <p>Implementations are not required to implement this method in any
* meaningful way; this may be a no-operation, and implementations may
* throw an {@link UnsupportedOperationException}.</p>
*
* @param in The buffer of new random bytes to add.
* @param offset The offset from whence to begin reading random bytes.
* @param length The number of random bytes to add.
* @exception IndexOutOfBoundsException If <i>offset</i>, <i>length</i>,
* or <i>offset</i>+<i>length</i> is out of bounds.
*/
void addRandomBytes(byte[] in, int offset, int length);
/**
* <p>Returns a clone copy of this instance.</p>
*
* @return a clone copy of this instance.
*/
Object clone() throws CloneNotSupportedException;
}

View File

@ -0,0 +1,73 @@
package gnu.crypto.prng;
// ----------------------------------------------------------------------------
// $Id: LimitReachedExceptionStandalone.java,v 1.1 2006-07-04 16:18:04 jrandom Exp $
//
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto 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 2, or (at your option)
// any later version.
//
// GNU Crypto 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; see the file COPYING. If not, write to the
//
// Free Software Foundation Inc.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301
// USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library. Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module. An independent module is a module which is
// not derived from or based on this library. If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so. If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------
/**
* A checked exception that indicates that a pseudo random number generated has
* reached its theoretical limit in generating random bytes.
*
* @version $Revision: 1.1 $
*/
public class LimitReachedExceptionStandalone extends Exception {
// Constants and variables
// -------------------------------------------------------------------------
// Constructor(s)
// -------------------------------------------------------------------------
public LimitReachedExceptionStandalone() {
super();
}
public LimitReachedExceptionStandalone(String msg) {
super(msg);
}
// Class methods
// -------------------------------------------------------------------------
// Instant methods
// -------------------------------------------------------------------------
}

View File

@ -0,0 +1,53 @@
/* RandomEventListenerStandalone.java -- event listener
Copyright (C) 2004 Free Software Foundation, Inc.
This file is part of GNU Crypto.
GNU Crypto 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 2, or (at your option) any
later version.
GNU Crypto 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; see the file COPYING. If not, write to the
Free Software Foundation Inc.,
51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301
USA
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package gnu.crypto.prng;
import java.util.EventListener;
/**
* An interface for entropy accumulators that will be notified of random
* events.
*/
public interface RandomEventListenerStandalone extends EventListener
{
void addRandomEvent(RandomEventStandalone event);
}

View File

@ -0,0 +1,82 @@
/* RandomEventStandalone.java -- a random event.
Copyright (C) 2004 Free Software Foundation, Inc.
This file is part of GNU Crypto.
GNU Crypto 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 2, or (at your option) any
later version.
GNU Crypto 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; see the file COPYING. If not, write to the
Free Software Foundation Inc.,
51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301
USA
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package gnu.crypto.prng;
import java.util.EventObject;
/**
* An interface for entropy accumulators that will be notified of random
* events.
*/
public class RandomEventStandalone extends EventObject
{
private final byte sourceNumber;
private final byte poolNumber;
private final byte[] data;
public RandomEventStandalone(Object source, byte sourceNumber, byte poolNumber,
byte[] data)
{
super(source);
this.sourceNumber = sourceNumber;
this.poolNumber = poolNumber;
if (data.length == 0 || data.length > 32)
throw new IllegalArgumentException("random events take between 1 and 32 bytes of data");
this.data = (byte[]) data.clone();
}
public byte getSourceNumber()
{
return sourceNumber;
}
public byte getPoolNumber()
{
return poolNumber;
}
public byte[] getData()
{
return data;
}
}

View File

@ -0,0 +1,24 @@
package net.i2p;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
/**
* Expose a version string
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.65 $ $Date: 2006-07-18 15:08:01 $";
public final static String VERSION = "0.6.1.22";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);
System.out.println("ID: " + ID);
}
}

View File

@ -0,0 +1,482 @@
package net.i2p;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import net.i2p.client.naming.NamingService;
import net.i2p.client.naming.PetNameDB;
import net.i2p.crypto.AESEngine;
import net.i2p.crypto.CryptixAESEngine;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.DummyDSAEngine;
import net.i2p.crypto.DummyElGamalEngine;
import net.i2p.crypto.DummyPooledRandomSource;
import net.i2p.crypto.ElGamalAESEngine;
import net.i2p.crypto.ElGamalEngine;
import net.i2p.crypto.HMAC256Generator;
import net.i2p.crypto.HMACGenerator;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.PersistentSessionKeyManager;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.stat.StatManager;
import net.i2p.util.Clock;
import net.i2p.util.LogManager;
import net.i2p.util.RandomSource;
import net.i2p.util.PooledRandomSource;
import net.i2p.util.FortunaRandomSource;
/**
* <p>Provide a base scope for accessing singletons that I2P exposes. Rather than
* using the traditional singleton, where any component can access the component
* in question directly, all of those I2P related singletons are exposed through
* a particular I2PAppContext. This helps not only with understanding their use
* and the components I2P exposes, but it also allows multiple isolated
* environments to operate concurrently within the same JVM - particularly useful
* for stubbing out implementations of the rooted components and simulating the
* software's interaction between multiple instances.</p>
*
* As a simplification, there is also a global context - if some component needs
* access to one of the singletons but doesn't have its own context from which
* to root itself, it binds to the I2PAppContext's globalAppContext(), which is
* the first context that was created within the JVM, or a new one if no context
* existed already. This functionality is often used within the I2P core for
* logging - e.g. <pre>
* private static final Log _log = new Log(someClass.class);
* </pre>
* It is for this reason that applications that care about working with multiple
* contexts should build their own context as soon as possible (within the main(..))
* so that any referenced components will latch on to that context instead of
* instantiating a new one. However, there are situations in which both can be
* relevent.
*
*/
public class I2PAppContext {
/** the context that components without explicit root are bound */
protected static I2PAppContext _globalAppContext;
private Properties _overrideProps;
private StatManager _statManager;
private SessionKeyManager _sessionKeyManager;
private NamingService _namingService;
private PetNameDB _petnameDb;
private ElGamalEngine _elGamalEngine;
private ElGamalAESEngine _elGamalAESEngine;
private AESEngine _AESEngine;
private LogManager _logManager;
private HMACGenerator _hmac;
private HMAC256Generator _hmac256;
private SHA256Generator _sha;
private Clock _clock;
private DSAEngine _dsa;
private RoutingKeyGenerator _routingKeyGenerator;
private RandomSource _random;
private KeyGenerator _keyGenerator;
private volatile boolean _statManagerInitialized;
private volatile boolean _sessionKeyManagerInitialized;
private volatile boolean _namingServiceInitialized;
private volatile boolean _petnameDbInitialized;
private volatile boolean _elGamalEngineInitialized;
private volatile boolean _elGamalAESEngineInitialized;
private volatile boolean _AESEngineInitialized;
private volatile boolean _logManagerInitialized;
private volatile boolean _hmacInitialized;
private volatile boolean _hmac256Initialized;
private volatile boolean _shaInitialized;
private volatile boolean _clockInitialized;
private volatile boolean _dsaInitialized;
private volatile boolean _routingKeyGeneratorInitialized;
private volatile boolean _randomInitialized;
private volatile boolean _keyGeneratorInitialized;
/**
* Pull the default context, creating a new one if necessary, else using
* the first one created.
*
*/
public static I2PAppContext getGlobalContext() {
synchronized (I2PAppContext.class) {
if (_globalAppContext == null) {
_globalAppContext = new I2PAppContext(false, null);
}
}
return _globalAppContext;
}
/**
* Lets root a brand new context
*
*/
public I2PAppContext() {
this(true, null);
}
/**
* Lets root a brand new context
*
*/
public I2PAppContext(Properties envProps) {
this(true, envProps);
}
/**
* @param doInit should this context be used as the global one (if necessary)?
*/
private I2PAppContext(boolean doInit, Properties envProps) {
if (doInit) {
synchronized (I2PAppContext.class) {
if (_globalAppContext == null)
_globalAppContext = this;
}
}
_overrideProps = envProps;
_statManager = null;
_sessionKeyManager = null;
_namingService = null;
_petnameDb = null;
_elGamalEngine = null;
_elGamalAESEngine = null;
_logManager = null;
_statManagerInitialized = false;
_sessionKeyManagerInitialized = false;
_namingServiceInitialized = false;
_elGamalEngineInitialized = false;
_elGamalAESEngineInitialized = false;
_logManagerInitialized = false;
}
/**
* Access the configuration attributes of this context, using properties
* provided during the context construction, or falling back on
* System.getProperty if no properties were provided during construction
* (or the specified prop wasn't included).
*
*/
public String getProperty(String propName) {
if (_overrideProps != null) {
if (_overrideProps.containsKey(propName))
return _overrideProps.getProperty(propName);
}
return System.getProperty(propName);
}
/**
* Access the configuration attributes of this context, using properties
* provided during the context construction, or falling back on
* System.getProperty if no properties were provided during construction
* (or the specified prop wasn't included).
*
*/
public String getProperty(String propName, String defaultValue) {
if (_overrideProps != null) {
if (_overrideProps.containsKey(propName))
return _overrideProps.getProperty(propName, defaultValue);
}
return System.getProperty(propName, defaultValue);
}
/**
* Access the configuration attributes of this context, listing the properties
* provided during the context construction, as well as the ones included in
* System.getProperties.
*
* @return set of Strings containing the names of defined system properties
*/
public Set getPropertyNames() {
Set names = new HashSet(System.getProperties().keySet());
if (_overrideProps != null)
names.addAll(_overrideProps.keySet());
return names;
}
/**
* The statistics component with which we can track various events
* over time.
*/
public StatManager statManager() {
if (!_statManagerInitialized) initializeStatManager();
return _statManager;
}
private void initializeStatManager() {
synchronized (this) {
if (_statManager == null)
_statManager = new StatManager(this);
_statManagerInitialized = true;
}
}
/**
* The session key manager which coordinates the sessionKey / sessionTag
* data. This component allows transparent operation of the
* ElGamal/AES+SessionTag algorithm, and contains all of the session tags
* for one particular application. If you want to seperate multiple apps
* to have their own sessionTags and sessionKeys, they should use different
* I2PAppContexts, and hence, different sessionKeyManagers.
*
*/
public SessionKeyManager sessionKeyManager() {
if (!_sessionKeyManagerInitialized) initializeSessionKeyManager();
return _sessionKeyManager;
}
private void initializeSessionKeyManager() {
synchronized (this) {
if (_sessionKeyManager == null)
_sessionKeyManager = new PersistentSessionKeyManager(this);
_sessionKeyManagerInitialized = true;
}
}
/**
* Pull up the naming service used in this context. The naming service itself
* works by querying the context's properties, so those props should be
* specified to customize the naming service exposed.
*/
public NamingService namingService() {
if (!_namingServiceInitialized) initializeNamingService();
return _namingService;
}
private void initializeNamingService() {
synchronized (this) {
if (_namingService == null) {
_namingService = NamingService.createInstance(this);
}
_namingServiceInitialized = true;
}
}
public PetNameDB petnameDb() {
if (!_petnameDbInitialized) initializePetnameDb();
return _petnameDb;
}
private void initializePetnameDb() {
synchronized (this) {
if (_petnameDb == null) {
_petnameDb = new PetNameDB();
}
_petnameDbInitialized = true;
}
}
/**
* This is the ElGamal engine used within this context. While it doesn't
* really have anything substantial that is context specific (the algorithm
* just does the algorithm), it does transparently use the context for logging
* its performance and activity. In addition, the engine can be swapped with
* the context's properties (though only someone really crazy should mess with
* it ;)
*/
public ElGamalEngine elGamalEngine() {
if (!_elGamalEngineInitialized) initializeElGamalEngine();
return _elGamalEngine;
}
private void initializeElGamalEngine() {
synchronized (this) {
if (_elGamalEngine == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_elGamalEngine = new DummyElGamalEngine(this);
else
_elGamalEngine = new ElGamalEngine(this);
}
_elGamalEngineInitialized = true;
}
}
/**
* Access the ElGamal/AES+SessionTag engine for this context. The algorithm
* makes use of the context's sessionKeyManager to coordinate transparent
* access to the sessionKeys and sessionTags, as well as the context's elGamal
* engine (which in turn keeps stats, etc).
*
*/
public ElGamalAESEngine elGamalAESEngine() {
if (!_elGamalAESEngineInitialized) initializeElGamalAESEngine();
return _elGamalAESEngine;
}
private void initializeElGamalAESEngine() {
synchronized (this) {
if (_elGamalAESEngine == null)
_elGamalAESEngine = new ElGamalAESEngine(this);
_elGamalAESEngineInitialized = true;
}
}
/**
* Ok, I'll admit it. there is no good reason for having a context specific
* AES engine. We dont really keep stats on it, since its just too fast to
* matter. Though for the crazy people out there, we do expose a way to
* disable it.
*/
public AESEngine aes() {
if (!_AESEngineInitialized) initializeAESEngine();
return _AESEngine;
}
private void initializeAESEngine() {
synchronized (this) {
if (_AESEngine == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_AESEngine = new AESEngine(this);
else
_AESEngine = new CryptixAESEngine(this);
}
_AESEngineInitialized = true;
}
}
/**
* Query the log manager for this context, which may in turn have its own
* set of configuration settings (loaded from the context's properties).
* Each context's logManager keeps its own isolated set of Log instances with
* their own log levels, output locations, and rotation configuration.
*/
public LogManager logManager() {
if (!_logManagerInitialized) initializeLogManager();
return _logManager;
}
private void initializeLogManager() {
synchronized (this) {
if (_logManager == null)
_logManager = new LogManager(this);
_logManagerInitialized = true;
}
}
/**
* There is absolutely no good reason to make this context specific,
* other than for consistency, and perhaps later we'll want to
* include some stats.
*/
public HMACGenerator hmac() {
if (!_hmacInitialized) initializeHMAC();
return _hmac;
}
private void initializeHMAC() {
synchronized (this) {
if (_hmac == null) {
_hmac= new HMACGenerator(this);
}
_hmacInitialized = true;
}
}
public HMAC256Generator hmac256() {
if (!_hmac256Initialized) initializeHMAC256();
return _hmac256;
}
private void initializeHMAC256() {
synchronized (this) {
if (_hmac256 == null) {
_hmac256 = new HMAC256Generator(this);
}
_hmac256Initialized = true;
}
}
/**
* Our SHA256 instance (see the hmac discussion for why its context specific)
*
*/
public SHA256Generator sha() {
if (!_shaInitialized) initializeSHA();
return _sha;
}
private void initializeSHA() {
synchronized (this) {
if (_sha == null)
_sha= new SHA256Generator(this);
_shaInitialized = true;
}
}
/**
* Our DSA engine (see HMAC and SHA above)
*
*/
public DSAEngine dsa() {
if (!_dsaInitialized) initializeDSA();
return _dsa;
}
private void initializeDSA() {
synchronized (this) {
if (_dsa == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_dsa = new DummyDSAEngine(this);
else
_dsa = new DSAEngine(this);
}
_dsaInitialized = true;
}
}
/**
* Component to generate ElGamal, DSA, and Session keys. For why it is in
* the appContext, see the DSA, HMAC, and SHA comments above.
*/
public KeyGenerator keyGenerator() {
if (!_keyGeneratorInitialized) initializeKeyGenerator();
return _keyGenerator;
}
private void initializeKeyGenerator() {
synchronized (this) {
if (_keyGenerator == null)
_keyGenerator = new KeyGenerator(this);
_keyGeneratorInitialized = true;
}
}
/**
* The context's synchronized clock, which is kept context specific only to
* enable simulators to play with clock skew among different instances.
*
*/
public Clock clock() {
if (!_clockInitialized) initializeClock();
return _clock;
}
private void initializeClock() {
synchronized (this) {
if (_clock == null)
_clock = new Clock(this);
_clockInitialized = true;
}
}
/**
* Determine how much do we want to mess with the keys to turn them
* into something we can route. This is context specific because we
* may want to test out how things react when peers don't agree on
* how to skew.
*
*/
public RoutingKeyGenerator routingKeyGenerator() {
if (!_routingKeyGeneratorInitialized) initializeRoutingKeyGenerator();
return _routingKeyGenerator;
}
private void initializeRoutingKeyGenerator() {
synchronized (this) {
if (_routingKeyGenerator == null)
_routingKeyGenerator = new RoutingKeyGenerator(this);
_routingKeyGeneratorInitialized = true;
}
}
/**
* [insert snarky comment here]
*
*/
public RandomSource random() {
if (!_randomInitialized) initializeRandom();
return _random;
}
private void initializeRandom() {
synchronized (this) {
if (_random == null) {
if (true)
_random = new FortunaRandomSource(this);
else if ("true".equals(getProperty("i2p.weakPRNG", "false")))
_random = new DummyPooledRandomSource(this);
else
_random = new PooledRandomSource(this);
}
_randomInitialized = true;
}
}
}

View File

@ -0,0 +1,50 @@
package net.i2p;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* Base class of I2P exceptions
*
* @author jrandom
*/
public class I2PException extends Exception {
private Throwable _source;
public I2PException() {
this(null, null);
}
public I2PException(String msg) {
this(msg, null);
}
public I2PException(String msg, Throwable source) {
super(msg);
_source = source;
}
public void printStackTrace() {
if (_source != null) _source.printStackTrace();
super.printStackTrace();
}
public void printStackTrace(PrintStream ps) {
if (_source != null) _source.printStackTrace(ps);
super.printStackTrace(ps);
}
public void printStackTrace(PrintWriter pw) {
if (_source != null) _source.printStackTrace(pw);
super.printStackTrace(pw);
}
}

View File

@ -0,0 +1,59 @@
package net.i2p.client.naming;
import java.lang.reflect.Constructor;
import java.util.Collection;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
import net.i2p.data.Address;
public abstract class AddressDB {
private final static Log _log = new Log(NamingService.class);
protected I2PAppContext _context;
/** what classname should be used as the address db impl? */
public static final String PROP_IMPL = "i2p.addressdb.impl";
private static final String DEFAULT_IMPL = "net.i2p.client.naming.FilesystemAddressDB";
/**
* The address db should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
protected AddressDB(I2PAppContext context) {
_context = context;
}
private AddressDB() { // nop
}
/**
* Get an address db instance. This method ensures that there
* will be only one address db instance (singleton) as well as
* choose the implementation from the "i2p.addressdb.impl" system
* property.
*/
public static final synchronized AddressDB createInstance(I2PAppContext context) {
AddressDB instance = null;
String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
try {
Class cls = Class.forName(impl);
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
instance = (AddressDB)con.newInstance(new Object[] { context });
} catch (Exception ex) {
_log.error("Cannot load address db implementation", ex);
instance = new DummyAddressDB(context); // fallback
}
return instance;
}
public abstract Address get(String hostname);
public abstract Address put(Address address);
public abstract Address remove(String hostname);
public abstract Address remove(Address address);
public abstract boolean contains(Address address);
public abstract boolean contains(String hostname);
public abstract Collection hostnames();
}

View File

@ -0,0 +1,42 @@
package net.i2p.client.naming;
import java.util.Iterator;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
import net.i2p.data.Address;
public class AddressDBNamingService extends NamingService {
private AddressDB _addressdb;
public AddressDBNamingService(I2PAppContext context) {
super(context);
_addressdb = AddressDB.createInstance(context);
}
private AddressDBNamingService() {
super(null);
}
public Destination lookup(String hostname) {
Address addr = _addressdb.get(hostname);
if (addr != null) {
return addr.getDestination();
} else {
// If we can't find hostname in the addressdb, assume it's a key.
return lookupBase64(hostname);
}
}
public String reverseLookup(Destination dest) {
Iterator iter = _addressdb.hostnames().iterator();
while (iter.hasNext()) {
Address addr = _addressdb.get((String)iter.next());
if (addr != null && addr.getDestination().equals(dest)) {
return addr.getHostname();
}
}
return null;
}
}

View File

@ -0,0 +1,42 @@
package net.i2p.client.naming;
import java.util.Collection;
import net.i2p.I2PAppContext;
import net.i2p.data.Address;
public class DummyAddressDB extends AddressDB {
public DummyAddressDB(I2PAppContext context) {
super(context);
}
public Address get(String hostname) {
return null;
}
public Address put(Address address) {
return null;
}
public Address remove(String hostname) {
return null;
}
public Address remove(Address address) {
return null;
}
public boolean contains(Address address) {
return false;
}
public boolean contains(String hostname) {
return false;
}
public Collection hostnames() {
return null;
}
}

View File

@ -0,0 +1,33 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
/**
* A Dummy naming service that can only handle base64 destinations.
*/
class DummyNamingService extends NamingService {
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
protected DummyNamingService(I2PAppContext context) { super(context); }
private DummyNamingService() { super(null); }
public Destination lookup(String hostname) {
return lookupBase64(hostname);
}
public String reverseLookup(Destination dest) {
return null;
}
}

View File

@ -0,0 +1,118 @@
package net.i2p.client.naming;
import java.util.Collection;
import java.util.Arrays;
import java.util.Properties;
import java.util.Iterator;
import java.io.*;
import net.i2p.I2PAppContext;
import net.i2p.data.Address;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
public class FilesystemAddressDB extends AddressDB {
public final static String PROP_ADDRESS_DIR = "i2p.addressdir";
public final static String DEFAULT_ADDRESS_DIR = "addressDb";
private final static Log _log = new Log(FilesystemAddressDB.class);
public FilesystemAddressDB(I2PAppContext context) {
super(context);
//If the address db directory doesn't exist, create it, using the
//contents of hosts.txt.
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File addrDir = new File(dir);
if (!addrDir.exists()) {
addrDir.mkdir();
Properties hosts = new Properties();
File hostsFile = new File("hosts.txt");
if (hostsFile.exists() && hostsFile.canRead()) {
try {
DataHelper.loadProps(hosts, hostsFile);
} catch (IOException ioe) {
_log.error("Error loading hosts file " + hostsFile, ioe);
}
}
Iterator iter = hosts.keySet().iterator();
while (iter.hasNext()) {
String hostname = (String)iter.next();
Address addr = new Address();
addr.setHostname(hostname);
addr.setDestination(hosts.getProperty(hostname));
put(addr);
}
}
}
public Address get(String hostname) {
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir, hostname);
if (f.exists() && f.canRead()) {
Address addr = new Address();
try {
addr.readBytes(new FileInputStream(f));
} catch (FileNotFoundException exp) {
return null;
} catch (DataFormatException exp) {
_log.error(f.getPath() + " is not a valid address file.");
return null;
} catch (IOException exp) {
_log.error("Error reading " + f.getPath());
return null;
}
return addr;
} else {
_log.warn(f.getPath() + " does not exist.");
return null;
}
}
public Address put(Address address) {
Address previous = get(address.getHostname());
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir, address.getHostname());
try {
address.writeBytes(new FileOutputStream(f));
} catch (Exception exp) {
_log.error("Error writing " + f.getPath(), exp);
}
return previous;
}
public Address remove(String hostname) {
Address previous = get(hostname);
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir, hostname);
f.delete();
return previous;
}
public Address remove(Address address) {
if (contains(address)) {
return remove(address.getHostname());
} else {
return null;
}
}
public boolean contains(Address address) {
Address inDb = get(address.getHostname());
return inDb.equals(address);
}
public boolean contains(String hostname) {
return hostnames().contains(hostname);
}
public Collection hostnames() {
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir);
return Arrays.asList(f.list());
}
}

View File

@ -0,0 +1,90 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* A naming service based on the "hosts.txt" file.
*/
public class HostsTxtNamingService extends NamingService {
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public HostsTxtNamingService(I2PAppContext context) { super(context); }
private HostsTxtNamingService() { super(null); }
/**
* If this system property is specified, the tunnel will read the
* given file for hostname=destKey values when resolving names
*/
public final static String PROP_HOSTS_FILE = "i2p.hostsfilelist";
/** default hosts.txt filename */
public final static String DEFAULT_HOSTS_FILE =
"privatehosts.txt,userhosts.txt,hosts.txt";
private final static Log _log = new Log(HostsTxtNamingService.class);
private List getFilenames() {
String list = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
StringTokenizer tok = new StringTokenizer(list, ",");
List rv = new ArrayList(tok.countTokens());
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
return rv;
}
public Destination lookup(String hostname) {
// check the list each time, reloading the file on each
// lookup
List filenames = getFilenames();
for (int i = 0; i < filenames.size(); i++) {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
try {
File f = new File(hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
DataHelper.loadProps(hosts, f, true);
String key = hosts.getProperty(hostname.toLowerCase());
if ( (key != null) && (key.trim().length() > 0) ) {
return lookupBase64(key);
}
} else {
_log.warn("Hosts file " + hostsfile + " does not exist.");
}
} catch (Exception ioe) {
_log.error("Error loading hosts file " + hostsfile, ioe);
}
// not found, continue to the next file
}
// If we can't find name in any of the hosts files,
// assume it's a key.
return lookupBase64(hostname);
}
public String reverseLookup(Destination dest) {
return null;
}
}

View File

@ -0,0 +1,60 @@
package net.i2p.client.naming;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
public class MetaNamingService extends NamingService {
private final static String PROP_NAME_SERVICES = "i2p.nameservicelist";
private final static String DEFAULT_NAME_SERVICES =
"net.i2p.client.naming.PetNameNamingService,net.i2p.client.naming.HostsTxtNamingService";
private List _services;
public MetaNamingService(I2PAppContext context) {
super(context);
String list = _context.getProperty(PROP_NAME_SERVICES, DEFAULT_NAME_SERVICES);
StringTokenizer tok = new StringTokenizer(list, ",");
_services = new ArrayList(tok.countTokens());
while (tok.hasMoreTokens()) {
try {
Class cls = Class.forName(tok.nextToken());
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
_services.add(con.newInstance(new Object[] { context }));
} catch (Exception ex) {
_services.add(new DummyNamingService(context)); // fallback
}
}
}
public Destination lookup(String hostname) {
Iterator iter = _services.iterator();
while (iter.hasNext()) {
NamingService ns = (NamingService)iter.next();
Destination dest = ns.lookup(hostname);
if (dest != null) {
return dest;
}
}
return lookupBase64(hostname);
}
public String reverseLookup(Destination dest) {
Iterator iter = _services.iterator();
while (iter.hasNext()) {
NamingService ns = (NamingService)iter.next();
String hostname = ns.reverseLookup(dest);
if (hostname != null) {
return hostname;
}
}
return null;
}
}

View File

@ -0,0 +1,92 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import java.lang.reflect.Constructor;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Naming services create a subclass of this class.
*/
public abstract class NamingService {
private final static Log _log = new Log(NamingService.class);
protected I2PAppContext _context;
/** what classname should be used as the naming service impl? */
public static final String PROP_IMPL = "i2p.naming.impl";
private static final String DEFAULT_IMPL = "net.i2p.client.naming.MetaNamingService";
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
protected NamingService(I2PAppContext context) {
_context = context;
}
private NamingService() { // nop
}
/**
* Look up a host name.
* @return the Destination for this host name, or
* <code>null</code> if name is unknown.
*/
public abstract Destination lookup(String hostname);
/**
* Reverse look up a destination
* @return a host name for this Destination, or <code>null</code>
* if none is known. It is safe for subclasses to always return
* <code>null</code> if no reverse lookup is possible.
*/
public abstract String reverseLookup(Destination dest);
/**
* Check if host name is valid Base64 encoded dest and return this
* dest in that case. Useful as a "fallback" in custom naming
* implementations.
*/
protected Destination lookupBase64(String hostname) {
try {
Destination result = new Destination();
result.fromBase64(hostname);
return result;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN)) _log.warn("Error translating [" + hostname + "]", dfe);
return null;
}
}
/**
* Get a naming service instance. This method ensures that there
* will be only one naming service instance (singleton) as well as
* choose the implementation from the "i2p.naming.impl" system
* property.
*/
public static final synchronized NamingService createInstance(I2PAppContext context) {
NamingService instance = null;
String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
try {
Class cls = Class.forName(impl);
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
instance = (NamingService)con.newInstance(new Object[] { context });
} catch (Exception ex) {
_log.error("Cannot loadNaming service implementation", ex);
instance = new DummyNamingService(context); // fallback
}
return instance;
}
}

View File

@ -0,0 +1,172 @@
package net.i2p.client.naming;
import java.util.*;
import net.i2p.data.DataHelper;
/**
*
*/
public class PetName {
private String _name;
private String _network;
private String _protocol;
private List _groups;
private boolean _isPublic;
private String _location;
public PetName() {
this(null, null, null, null);
}
public PetName(String name, String network, String protocol, String location) {
_name = name;
_network = network;
_protocol = protocol;
_location = location;
_groups = new ArrayList();
_isPublic = false;
}
/**
* @param dbLine name:network:protocol:isPublic:group1,group2,group3:location
*/
public PetName(String dbLine) {
_groups = new ArrayList();
StringTokenizer tok = new StringTokenizer(dbLine, ":\n", true);
int tokens = tok.countTokens();
//System.out.println("Tokens: " + tokens);
if (tokens < 7) {
return;
}
String s = tok.nextToken();
if (":".equals(s)) {
_name = null;
} else {
_name = s;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
_network = null;
} else {
_network = s;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
_protocol = null;
} else {
_protocol = s;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
_isPublic = false;
} else {
if ("true".equals(s))
_isPublic = true;
else
_isPublic = false;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
// noop
} else {
StringTokenizer gtok = new StringTokenizer(s, ",");
while (gtok.hasMoreTokens())
_groups.add(gtok.nextToken().trim());
s = tok.nextToken(); // skip past the :
}
while (tok.hasMoreTokens()) {
if (_location == null)
_location = tok.nextToken();
else
_location = _location + tok.nextToken();
}
}
public String getName() { return _name; }
public String getNetwork() { return _network; }
public String getProtocol() { return _protocol; }
public String getLocation() { return _location; }
public boolean getIsPublic() { return _isPublic; }
public int getGroupCount() { return _groups.size(); }
public String getGroup(int i) { return (String)_groups.get(i); }
public void setName(String name) { _name = name; }
public void setNetwork(String network) { _network = network; }
public void setProtocol(String protocol) { _protocol = protocol; }
public void setLocation(String location) { _location = location; }
public void setIsPublic(boolean pub) { _isPublic = pub; }
public void addGroup(String name) {
if ( (name != null) && (name.length() > 0) && (!_groups.contains(name)) )
_groups.add(name);
}
public void removeGroup(String name) { _groups.remove(name); }
public void setGroups(String groups) {
if (groups != null) {
_groups.clear();
StringTokenizer tok = new StringTokenizer(groups, ", \t");
while (tok.hasMoreTokens())
addGroup(tok.nextToken().trim());
} else {
_groups.clear();
}
}
public boolean isMember(String group) {
for (int i = 0; i < getGroupCount(); i++)
if (getGroup(i).equals(group))
return true;
return false;
}
public String toString() {
StringBuffer buf = new StringBuffer(256);
if (_name != null) buf.append(_name.trim());
buf.append(':');
if (_network != null) buf.append(_network.trim());
buf.append(':');
if (_protocol != null) buf.append(_protocol.trim());
buf.append(':').append(_isPublic).append(':');
if (_groups != null) {
for (int i = 0; i < _groups.size(); i++) {
buf.append(((String)_groups.get(i)).trim());
if (i + 1 < _groups.size())
buf.append(',');
}
}
buf.append(':');
if (_location != null) buf.append(_location.trim());
return buf.toString();
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof PetName) ) return false;
PetName pn = (PetName)obj;
return DataHelper.eq(_name, pn._name) &&
DataHelper.eq(_location, pn._location) &&
DataHelper.eq(_network, pn._network) &&
DataHelper.eq(_protocol, pn._protocol);
}
public int hashCode() {
int rv = 0;
rv += DataHelper.hashCode(_name);
rv += DataHelper.hashCode(_location);
rv += DataHelper.hashCode(_network);
rv += DataHelper.hashCode(_protocol);
return rv;
}
public static void main(String args[]) {
test("a:b:c:true:e:f");
test("a:::true::d");
test("a:::true::");
test("a:b::true::");
test(":::trye::");
test("a:b:c:true:e:http://foo.bar");
}
private static void test(String line) {
PetName pn = new PetName(line);
String val = pn.toString();
System.out.println("OK? " + val.equals(line) + ": " + line + " [" + val + "]");
}
}

View File

@ -0,0 +1,103 @@
package net.i2p.client.naming;
import java.io.*;
import java.util.*;
/**
*
*/
public class PetNameDB {
/** name (String) to PetName mapping */
private Map _names;
private String _path;
public PetNameDB() {
_names = Collections.synchronizedMap(new HashMap());
}
public PetName getByName(String name) {
if ( (name == null) || (name.length() <= 0) ) return null;
return (PetName)_names.get(name.toLowerCase());
}
public void add(PetName pn) {
if ( (pn == null) || (pn.getName() == null) ) return;
_names.put(pn.getName().toLowerCase(), pn);
}
public void clear() { _names.clear(); }
public boolean contains(PetName pn) { return _names.containsValue(pn); }
public boolean containsName(String name) {
if ( (name == null) || (name.length() <= 0) ) return false;
return _names.containsKey(name.toLowerCase());
}
public boolean isEmpty() { return _names.isEmpty(); }
public Iterator iterator() { return new ArrayList(_names.values()).iterator(); }
public void remove(PetName pn) {
if (pn != null) _names.remove(pn.getName().toLowerCase());
}
public void removeName(String name) {
if (name != null) _names.remove(name.toLowerCase());
}
public int size() { return _names.size(); }
public Set getNames() { return new HashSet(_names.keySet()); }
public List getGroups() {
List rv = new ArrayList();
for (Iterator iter = iterator(); iter.hasNext(); ) {
PetName name = (PetName)iter.next();
for (int i = 0; i < name.getGroupCount(); i++)
if (!rv.contains(name.getGroup(i)))
rv.add(name.getGroup(i));
}
return rv;
}
public PetName getByLocation(String location) {
if (location == null) return null;
synchronized (_names) {
for (Iterator iter = iterator(); iter.hasNext(); ) {
PetName name = (PetName)iter.next();
if ( (name.getLocation() != null) && (name.getLocation().trim().equals(location.trim())) )
return name;
}
}
return null;
}
public void load(String location) throws IOException {
_path = location;
File f = new File(location);
if (!f.exists()) return;
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
String line = null;
while ( (line = in.readLine()) != null) {
PetName name = new PetName(line);
if (name.getName() != null)
add(name);
}
} finally {
in.close();
}
}
public void store(String location) throws IOException {
Writer out = null;
try {
out = new OutputStreamWriter(new FileOutputStream(location), "UTF-8");
for (Iterator iter = iterator(); iter.hasNext(); ) {
PetName name = (PetName)iter.next();
if (name != null)
out.write(name.toString() + "\n");
}
} finally {
out.close();
}
}
public void store() throws IOException {
if (_path != null) {
store(_path);
}
}
}

View File

@ -0,0 +1,65 @@
package net.i2p.client.naming;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
public class PetNameNamingService extends NamingService {
private PetNameDB _petnameDb;
public final static String PROP_PETNAME_FILE = "i2p.petnamefile";
public final static String DEFAULT_PETNAME_FILE = "petnames.txt";
public PetNameNamingService(I2PAppContext context) {
super(context);
_petnameDb = _context.petnameDb();
String file = _context.getProperty(PROP_PETNAME_FILE, DEFAULT_PETNAME_FILE);
//If the petnamedb file doesn't exist, create it, using the
//contents of hosts.txt.
// File nameFile = new File(file);
// if (!nameFile.exists()) {
// Properties hosts = new Properties();
// File hostsFile = new File("hosts.txt");
// if (hostsFile.exists() && hostsFile.canRead()) {
// try {
// DataHelper.loadProps(hosts, hostsFile);
// } catch (IOException ioe) {
// }
// }
// Iterator iter = hosts.keySet().iterator();
// while (iter.hasNext()) {
// String hostname = (String)iter.next();
// PetName pn = new PetName(hostname, "i2p", "http", hosts.getProperty(hostname));
// _petnameDb.set(hostname, pn);
// }
// try {
// _petnameDb.store(file);
// } catch (IOException ioe) {
// }
// }
try {
_petnameDb.load(file);
} catch (IOException ioe) {
}
}
public Destination lookup(String hostname) {
PetName name = _petnameDb.getByName(hostname);
if (name != null && name.getNetwork().equalsIgnoreCase("i2p")) {
return lookupBase64(name.getLocation());
} else {
return lookupBase64(hostname);
}
}
public String reverseLookup(Destination dest) {
return _petnameDb.getByLocation(dest.toBase64()).getName();
}
}

View File

@ -0,0 +1,181 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
/**
* Dummy wrapper for AES cipher operation.
*
*/
public class AESEngine {
private Log _log;
private I2PAppContext _context;
public AESEngine(I2PAppContext ctx) {
_context = ctx;
_log = _context.logManager().getLog(AESEngine.class);
if (getClass() == AESEngine.class)
_log.warn("Warning: AES is disabled");
}
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param payloadIndex index into the payload to start encrypting
* @param out where to store the result
* @param outIndex where in out to start writing
* @param sessionKey private esession key to encrypt to
* @param iv IV for CBC
* @param length how much data to encrypt
*/
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
encrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param payloadIndex index into the payload to start encrypting
* @param out where to store the result
* @param outIndex where in out to start writing
* @param sessionKey private esession key to encrypt to
* @param iv IV for CBC
* @param length how much data to encrypt
*/
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
System.arraycopy(payload, payloadIndex, out, outIndex, length);
_log.warn("Warning: AES is disabled");
}
public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) {
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
int size = Hash.HASH_LENGTH
+ 4 // sizeof(payload)
+ payload.length;
int padding = ElGamalAESEngine.getPaddingSize(size, paddedSize);
byte data[] = new byte[size + padding];
Hash h = _context.sha().calculateHash(iv);
int cur = 0;
System.arraycopy(h.getData(), 0, data, cur, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
DataHelper.toLong(data, cur, 4, payload.length);
cur += 4;
System.arraycopy(payload, 0, data, cur, payload.length);
cur += payload.length;
byte paddingData[] = ElGamalAESEngine.getPadding(_context, size, paddedSize);
System.arraycopy(paddingData, 0, data, cur, paddingData.length);
encrypt(data, 0, data, 0, sessionKey, iv, data.length);
return data;
}
public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) {
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
byte decr[] = new byte[payload.length];
decrypt(payload, 0, decr, 0, sessionKey, iv, payload.length);
if (decr == null) {
_log.error("Error decrypting the data - payload " + payload.length + " decrypted to null");
return null;
}
int cur = 0;
byte h[] = _context.sha().calculateHash(iv).getData();
for (int i = 0; i < Hash.HASH_LENGTH; i++) {
if (decr[i] != h[i]) {
_log.error("Hash does not match [key=" + sessionKey + " / iv =" + DataHelper.toString(iv, iv.length)
+ "]", new Exception("Hash error"));
return null;
}
}
cur += Hash.HASH_LENGTH;
long len = DataHelper.fromLong(decr, cur, 4);
cur += 4;
if (cur + len > decr.length) {
_log.error("Not enough to read");
return null;
}
byte data[] = new byte[(int)len];
System.arraycopy(decr, cur, data, 0, (int)len);
return data;
}
/** Decrypt the data with the session key
* @param payload data to be decrypted
* @param payloadIndex index into the payload to start decrypting
* @param out where to store the cleartext
* @param outIndex where in out to start writing
* @param sessionKey private session key to decrypt to
* @param iv IV for CBC
* @param length how much data to decrypt
*/
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
decrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
/** Decrypt the data with the session key
* @param payload data to be decrypted
* @param payloadIndex index into the payload to start decrypting
* @param out where to store the cleartext
* @param outIndex where in out to start writing
* @param sessionKey private session key to decrypt to
* @param iv IV for CBC
* @param length how much data to decrypt
*/
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
System.arraycopy(payload, payloadIndex, out, outIndex, length);
_log.warn("Warning: AES is disabled");
}
public void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
System.arraycopy(payload, inIndex, out, outIndex, out.length - outIndex);
}
/** decrypt the data with the session key provided
* @param payload encrypted data
* @param sessionKey private session key
*/
public void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
System.arraycopy(payload, inIndex, rv, outIndex, rv.length - outIndex);
}
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
RandomSource.getInstance().nextBytes(iv);
byte sbuf[] = new byte[16];
RandomSource.getInstance().nextBytes(sbuf);
byte se[] = new byte[16];
ctx.aes().encrypt(sbuf, 0, se, 0, key, iv, sbuf.length);
byte sd[] = new byte[16];
ctx.aes().decrypt(se, 0, sd, 0, key, iv, se.length);
ctx.logManager().getLog(AESEngine.class).debug("Short test: " + DataHelper.eq(sd, sbuf));
byte lbuf[] = new byte[1024];
RandomSource.getInstance().nextBytes(sbuf);
byte le[] = ctx.aes().safeEncrypt(lbuf, key, iv, 2048);
byte ld[] = ctx.aes().safeDecrypt(le, key, iv);
ctx.logManager().getLog(AESEngine.class).debug("Long test: " + DataHelper.eq(ld, lbuf));
}
}

View File

@ -0,0 +1,460 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
/**
* This reads an underlying stream as written by AESOutputStream - AES256 encrypted
* in CBC mode with PKCS#5 padding, with the padding on each and every block of
* 16 bytes. This minimizes the overhead when communication is intermittent,
* rather than when streams of large sets of data are sent (in which case, the
* padding would be on a larger size - say, 1k, though in the worst case that
* would have 1023 bytes of padding, while in the worst case here, we only have
* 15 bytes of padding). So we have an expansion factor of 6.25%. c'est la vie
*
*/
public class AESInputStream extends FilterInputStream {
private Log _log;
private I2PAppContext _context;
private SessionKey _key;
private byte[] _lastBlock;
private boolean _eofFound;
private long _cumulativeRead; // how many read from the source stream
private long _cumulativePrepared; // how many bytes decrypted and added to _readyBuf
private long _cumulativePaddingStripped; // how many bytes have been stripped
/** read but not yet decrypted */
private byte _encryptedBuf[];
/** how many bytes have been added to the encryptedBuf since it was decrypted? */
private int _writesSinceDecrypt;
/** decrypted bytes ready for reading (first available == index of 0) */
private int _decryptedBuf[];
/** how many bytes are available for reading without decrypt? */
private int _decryptedSize;
private final static int BLOCK_SIZE = CryptixRijndael_Algorithm._BLOCK_SIZE;
public AESInputStream(I2PAppContext context, InputStream source, SessionKey key, byte[] iv) {
super(source);
_context = context;
_log = context.logManager().getLog(AESInputStream.class);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
_encryptedBuf = new byte[BLOCK_SIZE];
_writesSinceDecrypt = 0;
_decryptedBuf = new int[BLOCK_SIZE-1];
_decryptedSize = 0;
_cumulativePaddingStripped = 0;
_eofFound = false;
}
public int read() throws IOException {
while ((!_eofFound) && (_decryptedSize <= 0)) {
refill();
}
if (_decryptedSize > 0) {
int c = _decryptedBuf[0];
System.arraycopy(_decryptedBuf, 1, _decryptedBuf, 0, _decryptedBuf.length-1);
_decryptedSize--;
return c;
} else if (_eofFound) {
return -1;
} else {
throw new IOException("Not EOF, but none available? " + _decryptedSize
+ "/" + _writesSinceDecrypt
+ "/" + _cumulativeRead + "... impossible");
}
}
public int read(byte dest[]) throws IOException {
return read(dest, 0, dest.length);
}
public int read(byte dest[], int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
int val = read();
if (val == -1) {
// no more to read... can they expect more?
if (_eofFound && (i == 0)) {
if (_log.shouldLog(Log.DEBUG))
_log.info("EOF? " + _eofFound
+ "\nread=" + i + " decryptedSize=" + _decryptedSize
+ " \nencryptedSize=" + _writesSinceDecrypt
+ " \ntotal=" + _cumulativeRead
+ " \npadding=" + _cumulativePaddingStripped
+ " \nprepared=" + _cumulativePrepared);
return -1;
} else {
if (i != len)
if (_log.shouldLog(Log.DEBUG))
_log.info("non-terminal eof: " + _eofFound + " i=" + i + " len=" + len);
}
return i;
}
dest[off+i] = (byte)val;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the full buffer of size " + len);
return len;
}
public long skip(long numBytes) throws IOException {
for (long l = 0; l < numBytes; l++) {
int val = read();
if (val == -1) return l;
}
return numBytes;
}
public int available() throws IOException {
return _decryptedSize;
}
public void close() throws IOException {
in.close();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cumulative bytes read from source/decrypted/stripped: " + _cumulativeRead + "/"
+ _cumulativePrepared + "/" + _cumulativePaddingStripped + "] remaining [" + _decryptedSize + " ready, "
+ _writesSinceDecrypt + " still encrypted]");
}
public void mark(int readLimit) { // nop
}
public void reset() throws IOException {
throw new IOException("Reset not supported");
}
public boolean markSupported() {
return false;
}
/**
* Read at least one new byte from the underlying stream, and up to max new bytes,
* but not necessarily enough for a new decrypted block. This blocks until at least
* one new byte is read from the stream
*
*/
private void refill() throws IOException {
if ( (!_eofFound) && (_writesSinceDecrypt < BLOCK_SIZE) ) {
int read = in.read(_encryptedBuf, _writesSinceDecrypt, _encryptedBuf.length - _writesSinceDecrypt);
if (read == -1) {
_eofFound = true;
} else if (read > 0) {
_cumulativeRead += read;
_writesSinceDecrypt += read;
}
}
if (_writesSinceDecrypt == BLOCK_SIZE) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("We have " + _writesSinceDecrypt + " available to decrypt... doing so");
decryptBlock();
if ( (_writesSinceDecrypt > 0) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("Bytes left in the encrypted buffer after decrypt: "
+ _writesSinceDecrypt);
}
}
/**
* Decrypt the
*/
private void decryptBlock() throws IOException {
if (_writesSinceDecrypt != BLOCK_SIZE)
throw new IOException("Error decrypting - no data to decrypt");
if (_decryptedSize != 0)
throw new IOException("wtf, decrypted size is not 0? " + _decryptedSize);
_context.aes().decrypt(_encryptedBuf, 0, _encryptedBuf, 0, _key, _lastBlock, BLOCK_SIZE);
DataHelper.xor(_encryptedBuf, 0, _lastBlock, 0, _encryptedBuf, 0, BLOCK_SIZE);
int payloadBytes = countBlockPayload(_encryptedBuf, 0);
for (int i = 0; i < payloadBytes; i++) {
int c = _encryptedBuf[i];
if (c <= 0)
c += 256;
_decryptedBuf[i] = c;
}
_decryptedSize = payloadBytes;
_cumulativePaddingStripped += BLOCK_SIZE - payloadBytes;
_cumulativePrepared += payloadBytes;
System.arraycopy(_encryptedBuf, 0, _lastBlock, 0, BLOCK_SIZE);
_writesSinceDecrypt = 0;
}
/**
* How many non-padded bytes are there in the block starting at the given
* location.
*
* PKCS#5 specifies the padding for the block has the # of padding bytes
* located in the last byte of the block, and each of the padding bytes are
* equal to that value.
* e.g. in a 4 byte block:
* 0x0a padded would become
* 0x0a 0x03 0x03 0x03
* e.g. in a 4 byte block:
* 0x01 0x02 padded would become
* 0x01 0x02 0x02 0x02
*
* We use 16 byte blocks in this AES implementation
*
* @throws IOException if the padding is invalid
*/
private int countBlockPayload(byte data[], int startIndex) throws IOException {
int numPadBytes = data[startIndex + BLOCK_SIZE - 1];
if ((numPadBytes >= BLOCK_SIZE) || (numPadBytes <= 0)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("countBlockPayload on block index " + startIndex
+ numPadBytes + " is an invalid # of pad bytes");
throw new IOException("Invalid number of pad bytes (" + numPadBytes
+ ") for " + startIndex + " index");
}
// optional, but a really good idea: verify the padding
if (true) {
for (int i = BLOCK_SIZE - numPadBytes; i < BLOCK_SIZE; i++) {
if (data[startIndex + i] != (byte) numPadBytes) {
throw new IOException("Incorrect padding on decryption: data[" + i
+ "] = " + data[startIndex + i] + " not " + numPadBytes);
}
}
}
return BLOCK_SIZE - numPadBytes;
}
int remainingBytes() {
return _writesSinceDecrypt;
}
int readyBytes() {
return _decryptedSize;
}
/**
* Test AESOutputStream/AESInputStream
*/
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
try {
System.out.println("pwd=" + new java.io.File(".").getAbsolutePath());
System.out.println("Beginning");
runTest(ctx);
} catch (Throwable e) {
ctx.logManager().getLog(AESInputStream.class).error("Fail", e);
}
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
System.out.println("Done");
}
private static void runTest(I2PAppContext ctx) {
Log log = ctx.logManager().getLog(AESInputStream.class);
log.setMinimumPriority(Log.DEBUG);
byte orig[] = new byte[1024 * 32];
RandomSource.getInstance().nextBytes(orig);
//byte orig[] = "you are my sunshine, my only sunshine".getBytes();
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
byte iv[] = "there once was a".getBytes();
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 32KB data");
orig = new byte[20];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 20 byte data");
orig = new byte[3];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 3 byte data");
orig = new byte[0];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 0 byte data");
for (int i = 0; i <= 32768; i++) {
orig = new byte[i];
ctx.random().nextBytes(orig);
try {
log.info("Testing " + orig.length);
runTest(ctx, orig, key, iv);
} catch (RuntimeException re) {
log.error("Error testing " + orig.length);
throw re;
}
}
/*
orig = new byte[615280];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 615280 byte data");
*/
/*
for (int i = 0; i < 100; i++) {
orig = new byte[ctx.random().nextInt(1024*1024)];
ctx.random().nextBytes(orig);
try {
runTest(ctx, orig, key, iv);
} catch (RuntimeException re) {
log.error("Error testing " + orig.length);
throw re;
}
}
log.info("Done testing 100 random lengths");
*/
orig = new byte[32];
RandomSource.getInstance().nextBytes(orig);
try {
runOffsetTest(ctx, orig, key, iv);
} catch (Exception e) {
log.info("Error running offset test", e);
}
log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException ie) { // nop
}
}
private static void runTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
Log log = ctx.logManager().getLog(AESInputStream.class);
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
long endE = Clock.getInstance().now();
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted);
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(sin, buf);
if (read > 0) baos.write(buf, 0, read);
sin.close();
byte fin[] = baos.toByteArray();
long end = Clock.getInstance().now();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq) {
//log.info("Equal hashes. hash: " + origHash);
} else {
throw new RuntimeException("NOT EQUAL! len=" + orig.length + " read=" + read
+ "\norig: \t" + Base64.encode(orig) + "\nnew : \t"
+ Base64.encode(fin));
}
boolean ok = DataHelper.eq(orig, fin);
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (IOException ioe) {
log.error("ERROR transferring", ioe);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}
private static void runOffsetTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
Log log = ctx.logManager().getLog(AESInputStream.class);
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
long endE = Clock.getInstance().now();
log.info("Encrypted segment length: " + encrypted.length);
byte encryptedSegment[] = new byte[40];
System.arraycopy(encrypted, 0, encryptedSegment, 0, 40);
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedSegment);
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(sin, buf);
int remaining = sin.remainingBytes();
int readyBytes = sin.readyBytes();
log.info("Read: " + read);
if (read > 0) baos.write(buf, 0, read);
sin.close();
byte fin[] = baos.toByteArray();
log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
long end = Clock.getInstance().now();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq)
log.info("Equal hashes. hash: " + origHash);
else
throw new RuntimeException("NOT EQUAL! len=" + orig.length + "\norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
boolean ok = DataHelper.eq(orig, fin);
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (RuntimeException re) {
throw re;
} catch (IOException ioe) {
log.error("ERROR transferring", ioe);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}
}

View File

@ -0,0 +1,147 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
/**
* This writes everything as CBC with PKCS#5 padding, but each block is padded
* so as soon as a block is received it can be decrypted (rather than wait for
* an arbitrary number of blocks to arrive). That means that each block sent
* will contain exactly one padding byte (unless it was flushed with
* numBytes % (BLOCK_SIZE-1) != 0, in which case that last block will be padded
* with up to 15 bytes). So we have an expansion factor of 6.25%. c'est la vie
*
*/
public class AESOutputStream extends FilterOutputStream {
private Log _log;
private I2PAppContext _context;
private SessionKey _key;
private byte[] _lastBlock;
/**
* buffer containing the unwritten bytes. The first unwritten
* byte is _lastCommitted+1, and the last unwritten byte is _nextWrite-1
* (aka the next byte to be written on the array is _nextWrite)
*/
private byte[] _unencryptedBuf;
private byte _writeBlock[];
/** how many bytes have we been given since we flushed it to the stream? */
private int _writesSinceCommit;
private long _cumulativeProvided; // how many bytes provided to this stream
private long _cumulativeWritten; // how many bytes written to the underlying stream
private long _cumulativePadding; // how many bytes of padding written
public final static float EXPANSION_FACTOR = 1.0625f; // 6% overhead w/ the padding
private final static int BLOCK_SIZE = CryptixRijndael_Algorithm._BLOCK_SIZE;
private final static int MAX_BUF = 256;
public AESOutputStream(I2PAppContext context, OutputStream source, SessionKey key, byte[] iv) {
super(source);
_context = context;
_log = context.logManager().getLog(AESOutputStream.class);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
_unencryptedBuf = new byte[MAX_BUF];
_writeBlock = new byte[BLOCK_SIZE];
_writesSinceCommit = 0;
}
public void write(int val) throws IOException {
_cumulativeProvided++;
_unencryptedBuf[_writesSinceCommit++] = (byte)(val & 0xFF);
if (_writesSinceCommit == _unencryptedBuf.length)
doFlush();
}
public void write(byte src[]) throws IOException {
write(src, 0, src.length);
}
public void write(byte src[], int off, int len) throws IOException {
// i'm too lazy to unroll this into the partial writes (dealing with
// wrapping around the buffer size)
for (int i = 0; i < len; i++)
write(src[i+off]);
}
public void close() throws IOException {
flush();
out.close();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cumulative bytes provided to this stream / written out / padded: "
+ _cumulativeProvided + "/" + _cumulativeWritten + "/" + _cumulativePadding);
}
public void flush() throws IOException {
doFlush();
out.flush();
}
private void doFlush() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("doFlush(): writesSinceCommit=" + _writesSinceCommit);
writeEncrypted();
_writesSinceCommit = 0;
}
/**
* Encrypt an arbitrary size array with AES using CBC and PKCS#5 padding,
* write it to the stream, and set _lastBlock to the last encrypted
* block. This operation works by taking every (BLOCK_SIZE-1) bytes
* from the src, padding it with PKCS#5 (aka adding 0x01), and encrypting
* it. If the last block doesn't contain exactly (BLOCK_SIZE-1) bytes, it
* is padded with PKCS#5 as well (adding # padding bytes repeated that many
* times).
*
*/
private void writeEncrypted() throws IOException {
int numBlocks = _writesSinceCommit / (BLOCK_SIZE - 1);
if (_log.shouldLog(Log.INFO))
_log.info("writeE(): #=" + _writesSinceCommit + " blocks=" + numBlocks);
for (int i = 0; i < numBlocks; i++) {
DataHelper.xor(_unencryptedBuf, i * 15, _lastBlock, 0, _writeBlock, 0, 15);
// the padding byte for "full" blocks
_writeBlock[BLOCK_SIZE - 1] = (byte)(_lastBlock[BLOCK_SIZE - 1] ^ 0x01);
_context.aes().encrypt(_writeBlock, 0, _writeBlock, 0, _key, _lastBlock, BLOCK_SIZE);
out.write(_writeBlock);
System.arraycopy(_writeBlock, 0, _lastBlock, 0, BLOCK_SIZE);
_cumulativeWritten += BLOCK_SIZE;
_cumulativePadding++;
}
if (_writesSinceCommit % 15 != 0) {
// we need to do non trivial padding
int remainingBytes = _writesSinceCommit - numBlocks * 15;
int paddingBytes = BLOCK_SIZE - remainingBytes;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Padding " + _writesSinceCommit + " with " + paddingBytes + " bytes in " + (numBlocks+1) + " blocks");
System.arraycopy(_unencryptedBuf, numBlocks * 15, _writeBlock, 0, remainingBytes);
Arrays.fill(_writeBlock, remainingBytes, BLOCK_SIZE, (byte) paddingBytes);
DataHelper.xor(_writeBlock, 0, _lastBlock, 0, _writeBlock, 0, BLOCK_SIZE);
_context.aes().encrypt(_writeBlock, 0, _writeBlock, 0, _key, _lastBlock, BLOCK_SIZE);
out.write(_writeBlock);
System.arraycopy(_writeBlock, 0, _lastBlock, 0, BLOCK_SIZE);
_cumulativePadding += paddingBytes;
_cumulativeWritten += BLOCK_SIZE;
}
}
}

View File

@ -0,0 +1,275 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.security.InvalidKeyException;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Wrapper for AES cypher operation using Cryptix's Rijndael implementation. Implements
* CBC with a 16 byte IV.
* Problems:
* Only supports data of size mod 16 bytes - no inherent padding.
*
* @author jrandom, thecrypto
*/
public class CryptixAESEngine extends AESEngine {
private Log _log;
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
private final static boolean USE_FAKE_CRYPTO = false;
private final static byte FAKE_KEY = 0x2A;
private CryptixAESKeyCache _cache;
private static final ByteCache _prevCache = ByteCache.getInstance(16, 16);
public CryptixAESEngine(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(CryptixAESEngine.class);
_cache = new CryptixAESKeyCache();
}
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
encrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
if ( (payload == null) || (out == null) || (sessionKey == null) || (iv == null) )
throw new NullPointerException("invalid args to aes");
if (payload.length < payloadIndex + length)
throw new IllegalArgumentException("Payload is too short");
if (out.length < outIndex + length)
throw new IllegalArgumentException("Output is too short");
if (length <= 0)
throw new IllegalArgumentException("Length is too small");
if (length % 16 != 0)
throw new IllegalArgumentException("Only lengths mod 16 are supported here");
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
System.arraycopy(payload, payloadIndex, out, outIndex, length);
return;
}
int numblock = length / 16;
DataHelper.xor(iv, ivOffset, payload, payloadIndex, out, outIndex, 16);
encryptBlock(out, outIndex, sessionKey, out, outIndex);
for (int x = 1; x < numblock; x++) {
DataHelper.xor(out, outIndex + (x-1) * 16, payload, payloadIndex + x * 16, out, outIndex + x * 16, 16);
encryptBlock(out, outIndex + x * 16, sessionKey, out, outIndex + x * 16);
}
}
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
decrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null) )
throw new IllegalArgumentException("bad setup");
else if (out == null)
throw new IllegalArgumentException("out is null");
else if (out.length - outIndex < length)
throw new IllegalArgumentException("out is too small (out.length=" + out.length
+ " outIndex=" + outIndex + " length=" + length);
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
System.arraycopy(payload, payloadIndex, out, outIndex, length);
return ;
}
int numblock = length / 16;
if (length % 16 != 0) numblock++;
ByteArray prevA = _prevCache.acquire();
byte prev[] = prevA.getData();
ByteArray curA = _prevCache.acquire();
byte cur[] = curA.getData();
System.arraycopy(iv, ivOffset, prev, 0, 16);
for (int x = 0; x < numblock; x++) {
System.arraycopy(payload, payloadIndex + (x * 16), cur, 0, 16);
decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16));
DataHelper.xor(out, outIndex + x * 16, prev, 0, out, outIndex + x * 16, 16);
iv = prev; // just use IV to switch 'em around
prev = cur;
cur = iv;
}
/*
decryptBlock(payload, payloadIndex, sessionKey, out, outIndex);
DataHelper.xor(out, outIndex, iv, 0, out, outIndex, 16);
for (int x = 1; x < numblock; x++) {
decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16));
DataHelper.xor(out, outIndex + x * 16, payload, payloadIndex + (x - 1) * 16, out, outIndex + x * 16, 16);
}
*/
_prevCache.release(prevA);
_prevCache.release(curA);
}
public final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
if (sessionKey.getPreparedKey() == null) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
sessionKey.setPreparedKey(key);
} catch (InvalidKeyException ike) {
_log.log(Log.CRIT, "Invalid key", ike);
throw new IllegalArgumentException("wtf, invalid key? " + ike.getMessage());
}
}
CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, sessionKey.getPreparedKey(), 16);
}
/** decrypt the data with the session key provided
* @param payload encrypted data
* @param sessionKey private session key
*/
public final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
if ( (payload == null) || (rv == null) )
throw new IllegalArgumentException("null block args [payload=" + payload + " rv="+rv);
if (payload.length - inIndex > rv.length - outIndex)
throw new IllegalArgumentException("bad block args [payload.len=" + payload.length
+ " inIndex=" + inIndex + " rv.len=" + rv.length
+ " outIndex="+outIndex);
if (sessionKey.getPreparedKey() == null) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
sessionKey.setPreparedKey(key);
} catch (InvalidKeyException ike) {
_log.log(Log.CRIT, "Invalid key", ike);
throw new IllegalArgumentException("wtf, invalid key? " + ike.getMessage());
}
}
CryptixRijndael_Algorithm.blockDecrypt(payload, rv, inIndex, outIndex, sessionKey.getPreparedKey(), 16);
}
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
try {
testEDBlock(ctx);
testEDBlock2(ctx);
testED(ctx);
testED2(ctx);
//testFake(ctx);
//testNull(ctx);
} catch (Exception e) {
e.printStackTrace();
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
}
private static void testED(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[128];
byte encrypted[] = new byte[128];
byte decrypted[] = new byte[128];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length);
aes.decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length);
if (!DataHelper.eq(decrypted,orig))
throw new RuntimeException("full D(E(orig)) != orig");
else
System.out.println("full D(E(orig)) == orig");
}
private static void testED2(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[128];
byte data[] = new byte[128];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encrypt(orig, 0, data, 0, key, iv, data.length);
aes.decrypt(data, 0, data, 0, key, iv, data.length);
if (!DataHelper.eq(data,orig))
throw new RuntimeException("full D(E(orig)) != orig");
else
System.out.println("full D(E(orig)) == orig");
}
private static void testFake(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
SessionKey wrongKey = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[128];
byte encrypted[] = new byte[128];
byte decrypted[] = new byte[128];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length);
aes.decrypt(encrypted, 0, decrypted, 0, wrongKey, iv, encrypted.length);
if (DataHelper.eq(decrypted,orig))
throw new RuntimeException("full D(E(orig)) == orig when we used the wrong key!");
else
System.out.println("full D(E(orig)) != orig when we used the wrong key");
}
private static void testNull(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
SessionKey wrongKey = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[128];
byte encrypted[] = new byte[128];
byte decrypted[] = new byte[128];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length);
try {
aes.decrypt(null, 0, null, 0, wrongKey, iv, encrypted.length);
} catch (IllegalArgumentException iae) {
return;
}
throw new RuntimeException("full D(E(orig)) didn't fail when we used null!");
}
private static void testEDBlock(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[16];
byte encrypted[] = new byte[16];
byte decrypted[] = new byte[16];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encryptBlock(orig, 0, key, encrypted, 0);
aes.decryptBlock(encrypted, 0, key, decrypted, 0);
if (!DataHelper.eq(decrypted,orig))
throw new RuntimeException("block D(E(orig)) != orig");
else
System.out.println("block D(E(orig)) == orig");
}
private static void testEDBlock2(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[16];
byte data[] = new byte[16];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encryptBlock(orig, 0, key, data, 0);
aes.decryptBlock(data, 0, key, data, 0);
if (!DataHelper.eq(data,orig))
throw new RuntimeException("block D(E(orig)) != orig");
else
System.out.println("block D(E(orig)) == orig");
}
}

View File

@ -0,0 +1,70 @@
package net.i2p.crypto;
import java.util.ArrayList;
import java.util.List;
/**
* Cache the objects used in CryptixRijndael_Algorithm.makeKey to reduce
* memory churn. The KeyCacheEntry should be held onto as long as the
* data referenced in it is needed (which often is only one or two lines
* of code)
*
*/
public final class CryptixAESKeyCache {
private List _availableKeys;
private static final int KEYSIZE = 32; // 256bit AES
private static final int BLOCKSIZE = 16;
private static final int ROUNDS = CryptixRijndael_Algorithm.getRounds(KEYSIZE, BLOCKSIZE);
private static final int BC = BLOCKSIZE / 4;
private static final int KC = KEYSIZE / 4;
private static final int MAX_KEYS = 64;
public CryptixAESKeyCache() {
_availableKeys = new ArrayList(MAX_KEYS);
}
/**
* Get the next available structure, either from the cache or a brand new one
*
*/
public final KeyCacheEntry acquireKey() {
synchronized (_availableKeys) {
if (_availableKeys.size() > 0)
return (KeyCacheEntry)_availableKeys.remove(0);
}
return createNew();
}
/**
* Put this structure back onto the available cache for reuse
*
*/
public final void releaseKey(KeyCacheEntry key) {
synchronized (_availableKeys) {
if (_availableKeys.size() < MAX_KEYS)
_availableKeys.add(key);
}
}
public static final KeyCacheEntry createNew() {
KeyCacheEntry e = new KeyCacheEntry();
e.Ke = new int[ROUNDS + 1][BC]; // encryption round keys
e.Kd = new int[ROUNDS + 1][BC]; // decryption round keys
e.tk = new int[KC];
e.key = new Object[] { e.Ke, e.Kd };
return e;
}
/**
* all the data alloc'ed in a makeKey call
*/
public static final class KeyCacheEntry {
int[][] Ke;
int[][] Kd;
int[] tk;
Object[] key;
}
}

View File

@ -0,0 +1,902 @@
/*
* Copyright (c) 1997, 1998 Systemics Ltd on behalf of
* the Cryptix Development Team. All rights reserved.
*/
package net.i2p.crypto;
import java.io.PrintWriter;
import java.security.InvalidKeyException;
import net.i2p.util.Clock;
//...........................................................................
/**
* Rijndael --pronounced Reindaal-- is a variable block-size (128-, 192- and
* 256-bit), variable key-size (128-, 192- and 256-bit) symmetric cipher.<p>
*
* Rijndael was written by <a href="mailto:rijmen@esat.kuleuven.ac.be">Vincent
* Rijmen</a> and <a href="mailto:Joan.Daemen@village.uunet.be">Joan Daemen</a>.<p>
*
* Portions of this code are <b>Copyright</b> &copy; 1997, 1998
* <a href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the
* <a href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>.
* <br>All rights reserved.<p>
*
* @author Raif S. Naffah
* @author Paulo S. L. M. Barreto
*
* License is apparently available from http://www.cryptix.org/docs/license.html
*/
public final class CryptixRijndael_Algorithm // implicit no-argument constructor
{
// Debugging methods and variables
//...........................................................................
static final String _NAME = "Rijndael_Algorithm";
static final boolean _IN = true, _OUT = false;
static final boolean _RDEBUG = false;
static final int _debuglevel = 0; // RDEBUG ? Rijndael_Properties.getLevel(NAME): 0;
// static final PrintWriter err = RDEBUG ? Rijndael_Properties.getOutput() : null;
static final PrintWriter _err = new PrintWriter(new java.io.OutputStreamWriter(System.err));
static final boolean _TRACE = false; // Rijndael_Properties.isTraceable(NAME);
static void debug(String s) {
_err.println(">>> " + _NAME + ": " + s);
}
static void trace(boolean in, String s) {
if (_TRACE) _err.println((in ? "==> " : "<== ") + _NAME + "." + s);
}
static void trace(String s) {
if (_TRACE) _err.println("<=> " + _NAME + "." + s);
}
// Constants and variables
//...........................................................................
static final int _BLOCK_SIZE = 16; // default block size in bytes
static final int[] _alog = new int[256];
static final int[] _log = new int[256];
static final byte[] _S = new byte[256];
static final byte[] _Si = new byte[256];
static final int[] _T1 = new int[256];
static final int[] _T2 = new int[256];
static final int[] _T3 = new int[256];
static final int[] _T4 = new int[256];
static final int[] _T5 = new int[256];
static final int[] _T6 = new int[256];
static final int[] _T7 = new int[256];
static final int[] _T8 = new int[256];
static final int[] _U1 = new int[256];
static final int[] _U2 = new int[256];
static final int[] _U3 = new int[256];
static final int[] _U4 = new int[256];
static final byte[] _rcon = new byte[30];
static final int[][][] _shifts = new int[][][] { { { 0, 0}, { 1, 3}, { 2, 2}, { 3, 1}},
{ { 0, 0}, { 1, 5}, { 2, 4}, { 3, 3}},
{ { 0, 0}, { 1, 7}, { 3, 5}, { 4, 4}}};
private static final char[] _HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
'E', 'F'};
// Static code - to intialise S-boxes and T-boxes
//...........................................................................
static {
long time = Clock.getInstance().now();
if (_RDEBUG && _debuglevel > 6) {
System.out.println("Algorithm Name: Rijndael ver 0.1");
System.out.println("Electronic Codebook (ECB) Mode");
System.out.println();
}
int ROOT = 0x11B;
int i, j = 0;
//
// produce log and alog tables, needed for multiplying in the
// field GF(2^m) (generator = 3)
//
_alog[0] = 1;
for (i = 1; i < 256; i++) {
j = (_alog[i - 1] << 1) ^ _alog[i - 1];
if ((j & 0x100) != 0) j ^= ROOT;
_alog[i] = j;
}
for (i = 1; i < 255; i++)
_log[_alog[i]] = i;
byte[][] A = new byte[][] { { 1, 1, 1, 1, 1, 0, 0, 0}, { 0, 1, 1, 1, 1, 1, 0, 0}, { 0, 0, 1, 1, 1, 1, 1, 0},
{ 0, 0, 0, 1, 1, 1, 1, 1}, { 1, 0, 0, 0, 1, 1, 1, 1}, { 1, 1, 0, 0, 0, 1, 1, 1},
{ 1, 1, 1, 0, 0, 0, 1, 1}, { 1, 1, 1, 1, 0, 0, 0, 1}};
byte[] B = new byte[] { 0, 1, 1, 0, 0, 0, 1, 1};
//
// substitution box based on F^{-1}(x)
//
int t;
byte[][] box = new byte[256][8];
box[1][7] = 1;
for (i = 2; i < 256; i++) {
j = _alog[255 - _log[i]];
for (t = 0; t < 8; t++)
box[i][t] = (byte) ((j >>> (7 - t)) & 0x01);
}
//
// affine transform: box[i] <- B + A*box[i]
//
byte[][] cox = new byte[256][8];
for (i = 0; i < 256; i++)
for (t = 0; t < 8; t++) {
cox[i][t] = B[t];
for (j = 0; j < 8; j++)
cox[i][t] ^= A[t][j] * box[i][j];
}
//
// S-boxes and inverse S-boxes
//
for (i = 0; i < 256; i++) {
_S[i] = (byte) (cox[i][0] << 7);
for (t = 1; t < 8; t++)
_S[i] ^= cox[i][t] << (7 - t);
_Si[_S[i] & 0xFF] = (byte) i;
}
//
// T-boxes
//
byte[][] G = new byte[][] { { 2, 1, 1, 3}, { 3, 2, 1, 1}, { 1, 3, 2, 1}, { 1, 1, 3, 2}};
byte[][] AA = new byte[4][8];
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++)
AA[i][j] = G[i][j];
AA[i][i + 4] = 1;
}
byte pivot, tmp;
byte[][] iG = new byte[4][4];
for (i = 0; i < 4; i++) {
pivot = AA[i][i];
if (pivot == 0) {
t = i + 1;
while ((AA[t][i] == 0) && (t < 4))
t++;
if (t == 4)
throw new RuntimeException("G matrix is not invertible");
for (j = 0; j < 8; j++) {
tmp = AA[i][j];
AA[i][j] = AA[t][j];
AA[t][j] = tmp;
}
pivot = AA[i][i];
}
for (j = 0; j < 8; j++)
if (AA[i][j] != 0) AA[i][j] = (byte) _alog[(255 + _log[AA[i][j] & 0xFF] - _log[pivot & 0xFF]) % 255];
for (t = 0; t < 4; t++)
if (i != t) {
for (j = i + 1; j < 8; j++)
AA[t][j] ^= mul(AA[i][j], AA[t][i]);
AA[t][i] = 0;
}
}
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
iG[i][j] = AA[i][j + 4];
int s;
for (t = 0; t < 256; t++) {
s = _S[t];
_T1[t] = mul4(s, G[0]);
_T2[t] = mul4(s, G[1]);
_T3[t] = mul4(s, G[2]);
_T4[t] = mul4(s, G[3]);
s = _Si[t];
_T5[t] = mul4(s, iG[0]);
_T6[t] = mul4(s, iG[1]);
_T7[t] = mul4(s, iG[2]);
_T8[t] = mul4(s, iG[3]);
_U1[t] = mul4(t, iG[0]);
_U2[t] = mul4(t, iG[1]);
_U3[t] = mul4(t, iG[2]);
_U4[t] = mul4(t, iG[3]);
}
//
// round constants
//
_rcon[0] = 1;
int r = 1;
for (t = 1; t < 30;)
_rcon[t++] = (byte) (r = mul(2, r));
time = Clock.getInstance().now() - time;
if (_RDEBUG && _debuglevel > 8) {
System.out.println("==========");
System.out.println();
System.out.println("Static Data");
System.out.println();
System.out.println("S[]:");
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++)
System.out.print("0x" + byteToString(_S[i * 16 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("Si[]:");
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++)
System.out.print("0x" + byteToString(_Si[i * 16 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("iG[]:");
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + byteToString(iG[i][j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T1[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T1[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T2[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T2[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T3[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T3[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T4[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T4[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T5[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T5[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T6[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T6[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T7[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T7[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("T8[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_T8[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("U1[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_U1[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("U2[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_U2[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("U3[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_U3[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("U4[]:");
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++)
System.out.print("0x" + intToString(_U4[i * 4 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("rcon[]:");
for (i = 0; i < 5; i++) {
for (j = 0; j < 6; j++)
System.out.print("0x" + byteToString(_rcon[i * 6 + j]) + ", ");
System.out.println();
}
System.out.println();
System.out.println("Total initialization time: " + time + " ms.");
System.out.println();
}
}
// multiply two elements of GF(2^m)
static final int mul(int a, int b) {
return (a != 0 && b != 0) ? _alog[(_log[a & 0xFF] + _log[b & 0xFF]) % 255] : 0;
}
// convenience method used in generating Transposition boxes
static final int mul4(int a, byte[] b) {
if (a == 0) return 0;
a = _log[a & 0xFF];
int a0 = (b[0] != 0) ? _alog[(a + _log[b[0] & 0xFF]) % 255] & 0xFF : 0;
int a1 = (b[1] != 0) ? _alog[(a + _log[b[1] & 0xFF]) % 255] & 0xFF : 0;
int a2 = (b[2] != 0) ? _alog[(a + _log[b[2] & 0xFF]) % 255] & 0xFF : 0;
int a3 = (b[3] != 0) ? _alog[(a + _log[b[3] & 0xFF]) % 255] & 0xFF : 0;
return a0 << 24 | a1 << 16 | a2 << 8 | a3;
}
// Basic API methods
//...........................................................................
/**
* Convenience method to expand a user-supplied key material into a
* session key, assuming Rijndael's default block size (128-bit).
*
* @param k The 128/192/256-bit user-key to use.
* @exception InvalidKeyException If the key is invalid.
*/
public static final Object makeKey(byte[] k) throws InvalidKeyException {
return makeKey(k, _BLOCK_SIZE);
}
/**
* Convenience method to encrypt exactly one block of plaintext, assuming
* Rijndael's default block size (128-bit).
*
* @param in The plaintext.
* @param result The resulting ciphertext.
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for encryption.
*/
public static final void blockEncrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey) {
if (_RDEBUG) trace(_IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
int[][] Ke = (int[][]) ((Object[]) sessionKey)[0]; // extract encryption round keys
int ROUNDS = Ke.length - 1;
int[] Ker = Ke[0];
// plaintext to ints + key
int t0 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Ker[0];
int t1 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Ker[1];
int t2 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Ker[2];
int t3 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Ker[3];
int a0, a1, a2, a3;
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
Ker = Ke[r];
a0 = (_T1[(t0 >>> 24) & 0xFF] ^ _T2[(t1 >>> 16) & 0xFF] ^ _T3[(t2 >>> 8) & 0xFF] ^ _T4[t3 & 0xFF]) ^ Ker[0];
a1 = (_T1[(t1 >>> 24) & 0xFF] ^ _T2[(t2 >>> 16) & 0xFF] ^ _T3[(t3 >>> 8) & 0xFF] ^ _T4[t0 & 0xFF]) ^ Ker[1];
a2 = (_T1[(t2 >>> 24) & 0xFF] ^ _T2[(t3 >>> 16) & 0xFF] ^ _T3[(t0 >>> 8) & 0xFF] ^ _T4[t1 & 0xFF]) ^ Ker[2];
a3 = (_T1[(t3 >>> 24) & 0xFF] ^ _T2[(t0 >>> 16) & 0xFF] ^ _T3[(t1 >>> 8) & 0xFF] ^ _T4[t2 & 0xFF]) ^ Ker[3];
t0 = a0;
t1 = a1;
t2 = a2;
t3 = a3;
if (_RDEBUG && _debuglevel > 6)
System.out.println("CT" + r + "=" + intToString(t0) + intToString(t1) + intToString(t2)
+ intToString(t3));
}
// last round is special
Ker = Ke[ROUNDS];
int tt = Ker[0];
result[outOffset++] = (byte) (_S[(t0 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_S[(t1 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_S[(t2 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_S[t3 & 0xFF] ^ tt);
tt = Ker[1];
result[outOffset++] = (byte) (_S[(t1 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_S[(t2 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_S[(t3 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_S[t0 & 0xFF] ^ tt);
tt = Ker[2];
result[outOffset++] = (byte) (_S[(t2 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_S[(t3 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_S[(t0 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_S[t1 & 0xFF] ^ tt);
tt = Ker[3];
result[outOffset++] = (byte) (_S[(t3 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_S[(t0 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_S[(t1 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_S[t2 & 0xFF] ^ tt);
if (_RDEBUG && _debuglevel > 6) {
System.out.println("CT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockEncrypt()");
}
/**
* Convenience method to decrypt exactly one block of plaintext, assuming
* Rijndael's default block size (128-bit).
*
* @param in The ciphertext.
* @param result The resulting ciphertext
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for decryption.
*/
public static final void blockDecrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey) {
if (in.length - inOffset > result.length - outOffset)
throw new IllegalArgumentException("result too small: in.len=" + in.length + " in.offset=" + inOffset
+ " result.len=" + result.length + " result.offset=" + outOffset);
if (in.length - inOffset <= 15)
throw new IllegalArgumentException("data too small: " + in.length + " inOffset: " + inOffset);
if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]; // extract decryption round keys
int ROUNDS = Kd.length - 1;
int[] Kdr = Kd[0];
// ciphertext to ints + key
int t0 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Kdr[0];
int t1 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Kdr[1];
int t2 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Kdr[2];
int t3 = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Kdr[3];
int a0, a1, a2, a3;
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
Kdr = Kd[r];
a0 = (_T5[(t0 >>> 24) & 0xFF] ^ _T6[(t3 >>> 16) & 0xFF] ^ _T7[(t2 >>> 8) & 0xFF] ^ _T8[t1 & 0xFF]) ^ Kdr[0];
a1 = (_T5[(t1 >>> 24) & 0xFF] ^ _T6[(t0 >>> 16) & 0xFF] ^ _T7[(t3 >>> 8) & 0xFF] ^ _T8[t2 & 0xFF]) ^ Kdr[1];
a2 = (_T5[(t2 >>> 24) & 0xFF] ^ _T6[(t1 >>> 16) & 0xFF] ^ _T7[(t0 >>> 8) & 0xFF] ^ _T8[t3 & 0xFF]) ^ Kdr[2];
a3 = (_T5[(t3 >>> 24) & 0xFF] ^ _T6[(t2 >>> 16) & 0xFF] ^ _T7[(t1 >>> 8) & 0xFF] ^ _T8[t0 & 0xFF]) ^ Kdr[3];
t0 = a0;
t1 = a1;
t2 = a2;
t3 = a3;
if (_RDEBUG && _debuglevel > 6)
System.out.println("PT" + r + "=" + intToString(t0) + intToString(t1) + intToString(t2)
+ intToString(t3));
}
// last round is special
Kdr = Kd[ROUNDS];
int tt = Kdr[0];
result[outOffset++] = (byte) (_Si[(t0 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_Si[(t3 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_Si[(t2 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_Si[t1 & 0xFF] ^ tt);
tt = Kdr[1];
result[outOffset++] = (byte) (_Si[(t1 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_Si[(t0 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_Si[(t3 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_Si[t2 & 0xFF] ^ tt);
tt = Kdr[2];
result[outOffset++] = (byte) (_Si[(t2 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_Si[(t1 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_Si[(t0 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_Si[t3 & 0xFF] ^ tt);
tt = Kdr[3];
result[outOffset++] = (byte) (_Si[(t3 >>> 24) & 0xFF] ^ (tt >>> 24));
result[outOffset++] = (byte) (_Si[(t2 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_Si[(t1 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_Si[t0 & 0xFF] ^ tt);
if (_RDEBUG && _debuglevel > 6) {
System.out.println("PT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockDecrypt()");
}
/** A basic symmetric encryption/decryption test. */
public static boolean self_test() {
return self_test(_BLOCK_SIZE);
}
// Rijndael own methods
//...........................................................................
/** @return The default length in bytes of the Algorithm input block. */
public static final int blockSize() {
return _BLOCK_SIZE;
}
/**
* Expand a user-supplied key material into a session key.
*
* @param k The 128/192/256-bit user-key to use.
* @param blockSize The block size in bytes of this Rijndael.
* @exception InvalidKeyException If the key is invalid.
*/
public static final/* synchronized */Object makeKey(byte[] k, int blockSize) throws InvalidKeyException {
return makeKey(k, blockSize, null);
}
public static final/* synchronized */Object makeKey(byte[] k, int blockSize, CryptixAESKeyCache.KeyCacheEntry keyData) throws InvalidKeyException {
if (_RDEBUG) trace(_IN, "makeKey(" + k + ", " + blockSize + ")");
if (k == null) throw new InvalidKeyException("Empty key");
if (!(k.length == 16 || k.length == 24 || k.length == 32))
throw new InvalidKeyException("Incorrect key length");
int ROUNDS = getRounds(k.length, blockSize);
int BC = blockSize / 4;
int[][] Ke = null; // new int[ROUNDS + 1][BC]; // encryption round keys
int[][] Kd = null; // new int[ROUNDS + 1][BC]; // decryption round keys
int ROUND_KEY_COUNT = (ROUNDS + 1) * BC;
int KC = k.length / 4;
int[] tk = null; // new int[KC];
int i, j;
if (keyData == null) {
Ke = new int[ROUNDS + 1][BC];
Kd = new int[ROUNDS + 1][BC];
tk = new int[KC];
} else {
Ke = keyData.Ke;
Kd = keyData.Kd;
tk = keyData.tk;
}
// copy user material bytes into temporary ints
for (i = 0, j = 0; i < KC;)
tk[i++] = (k[j++] & 0xFF) << 24 | (k[j++] & 0xFF) << 16 | (k[j++] & 0xFF) << 8 | (k[j++] & 0xFF);
// copy values into round key arrays
int t = 0;
for (j = 0; (j < KC) && (t < ROUND_KEY_COUNT); j++, t++) {
Ke[t / BC][t % BC] = tk[j];
Kd[ROUNDS - (t / BC)][t % BC] = tk[j];
}
int tt, rconpointer = 0;
while (t < ROUND_KEY_COUNT) {
// extrapolate using phi (the round key evolution function)
tt = tk[KC - 1];
tk[0] ^= (_S[(tt >>> 16) & 0xFF] & 0xFF) << 24 ^ (_S[(tt >>> 8) & 0xFF] & 0xFF) << 16
^ (_S[tt & 0xFF] & 0xFF) << 8 ^ (_S[(tt >>> 24) & 0xFF] & 0xFF)
^ (_rcon[rconpointer++] & 0xFF) << 24;
if (KC != 8)
for (i = 1, j = 0; i < KC;) {
//tk[i++] ^= tk[j++];
// The above line replaced with the code below in order to work around
// a bug in the kjc-1.4F java compiler (which has been reported).
tk[i] ^= tk[j++];
i++;
}
else {
for (i = 1, j = 0; i < KC / 2;) {
//tk[i++] ^= tk[j++];
// The above line replaced with the code below in order to work around
// a bug in the kjc-1.4F java compiler (which has been reported).
tk[i] ^= tk[j++];
i++;
}
tt = tk[KC / 2 - 1];
tk[KC / 2] ^= (_S[tt & 0xFF] & 0xFF) ^ (_S[(tt >>> 8) & 0xFF] & 0xFF) << 8
^ (_S[(tt >>> 16) & 0xFF] & 0xFF) << 16 ^ (_S[(tt >>> 24) & 0xFF] & 0xFF) << 24;
for (j = KC / 2, i = j + 1; i < KC;) {
//tk[i++] ^= tk[j++];
// The above line replaced with the code below in order to work around
// a bug in the kjc-1.4F java compiler (which has been reported).
tk[i] ^= tk[j++];
i++;
}
}
// copy values into round key arrays
for (j = 0; (j < KC) && (t < ROUND_KEY_COUNT); j++, t++) {
Ke[t / BC][t % BC] = tk[j];
Kd[ROUNDS - (t / BC)][t % BC] = tk[j];
}
}
for (int r = 1; r < ROUNDS; r++)
// inverse MixColumn where needed
for (j = 0; j < BC; j++) {
tt = Kd[r][j];
Kd[r][j] = _U1[(tt >>> 24) & 0xFF] ^ _U2[(tt >>> 16) & 0xFF] ^ _U3[(tt >>> 8) & 0xFF] ^ _U4[tt & 0xFF];
}
// assemble the encryption (Ke) and decryption (Kd) round keys into
// one sessionKey object
Object[] sessionKey = null;
if (keyData == null)
sessionKey = new Object[] { Ke, Kd};
else
sessionKey = keyData.key;
if (_RDEBUG) trace(_OUT, "makeKey()");
return sessionKey;
}
/**
* Encrypt exactly one block of plaintext.
*
* @param in The plaintext.
* @param result The resulting ciphertext.
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for encryption.
* @param blockSize The block size in bytes of this Rijndael.
*/
public static final void blockEncrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey, int blockSize) {
if (blockSize == _BLOCK_SIZE) {
blockEncrypt(in, result, inOffset, outOffset, sessionKey);
return;
}
if (_RDEBUG) trace(_IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
Object[] sKey = (Object[]) sessionKey; // extract encryption round keys
int[][] Ke = (int[][]) sKey[0];
int BC = blockSize / 4;
int ROUNDS = Ke.length - 1;
int SC = BC == 4 ? 0 : (BC == 6 ? 1 : 2);
int s1 = _shifts[SC][1][0];
int s2 = _shifts[SC][2][0];
int s3 = _shifts[SC][3][0];
int[] a = new int[BC];
int[] t = new int[BC]; // temporary work array
int i;
int j = outOffset;
int tt;
for (i = 0; i < BC; i++)
// plaintext to ints + key
t[i] = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Ke[0][i];
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
for (i = 0; i < BC; i++)
a[i] = (_T1[(t[i] >>> 24) & 0xFF] ^ _T2[(t[(i + s1) % BC] >>> 16) & 0xFF]
^ _T3[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ _T4[t[(i + s3) % BC] & 0xFF])
^ Ke[r][i];
System.arraycopy(a, 0, t, 0, BC);
if (_RDEBUG && _debuglevel > 6) System.out.println("CT" + r + "=" + toString(t));
}
for (i = 0; i < BC; i++) { // last round is special
tt = Ke[ROUNDS][i];
result[j++] = (byte) (_S[(t[i] >>> 24) & 0xFF] ^ (tt >>> 24));
result[j++] = (byte) (_S[(t[(i + s1) % BC] >>> 16) & 0xFF] ^ (tt >>> 16));
result[j++] = (byte) (_S[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ (tt >>> 8));
result[j++] = (byte) (_S[t[(i + s3) % BC] & 0xFF] ^ tt);
}
if (_RDEBUG && _debuglevel > 6) {
System.out.println("CT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockEncrypt()");
}
/**
* Decrypt exactly one block of ciphertext.
*
* @param in The ciphertext.
* @param result The resulting ciphertext.
* @param inOffset Index of in from which to start considering data.
* @param sessionKey The session key to use for decryption.
* @param blockSize The block size in bytes of this Rijndael.
*/
public static final void blockDecrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey, int blockSize) {
if (blockSize == _BLOCK_SIZE) {
blockDecrypt(in, result, inOffset, outOffset, sessionKey);
return;
}
if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
Object[] sKey = (Object[]) sessionKey; // extract decryption round keys
int[][] Kd = (int[][]) sKey[1];
int BC = blockSize / 4;
int ROUNDS = Kd.length - 1;
int SC = BC == 4 ? 0 : (BC == 6 ? 1 : 2);
int s1 = _shifts[SC][1][1];
int s2 = _shifts[SC][2][1];
int s3 = _shifts[SC][3][1];
int[] a = new int[BC];
int[] t = new int[BC]; // temporary work array
int i;
int j = outOffset;
int tt;
for (i = 0; i < BC; i++)
// ciphertext to ints + key
t[i] = ((in[inOffset++] & 0xFF) << 24 | (in[inOffset++] & 0xFF) << 16 | (in[inOffset++] & 0xFF) << 8 | (in[inOffset++] & 0xFF))
^ Kd[0][i];
for (int r = 1; r < ROUNDS; r++) { // apply round transforms
for (i = 0; i < BC; i++)
a[i] = (_T5[(t[i] >>> 24) & 0xFF] ^ _T6[(t[(i + s1) % BC] >>> 16) & 0xFF]
^ _T7[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ _T8[t[(i + s3) % BC] & 0xFF])
^ Kd[r][i];
System.arraycopy(a, 0, t, 0, BC);
if (_RDEBUG && _debuglevel > 6) System.out.println("PT" + r + "=" + toString(t));
}
for (i = 0; i < BC; i++) { // last round is special
tt = Kd[ROUNDS][i];
result[j++] = (byte) (_Si[(t[i] >>> 24) & 0xFF] ^ (tt >>> 24));
result[j++] = (byte) (_Si[(t[(i + s1) % BC] >>> 16) & 0xFF] ^ (tt >>> 16));
result[j++] = (byte) (_Si[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ (tt >>> 8));
result[j++] = (byte) (_Si[t[(i + s3) % BC] & 0xFF] ^ tt);
}
if (_RDEBUG && _debuglevel > 6) {
System.out.println("PT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockDecrypt()");
}
/** A basic symmetric encryption/decryption test for a given key size. */
private static boolean self_test(int keysize) {
if (_RDEBUG) trace(_IN, "self_test(" + keysize + ")");
boolean ok = false;
try {
byte[] kb = new byte[keysize];
byte[] pt = new byte[_BLOCK_SIZE];
int i;
for (i = 0; i < keysize; i++)
kb[i] = (byte) i;
for (i = 0; i < _BLOCK_SIZE; i++)
pt[i] = (byte) i;
if (_RDEBUG && _debuglevel > 6) {
System.out.println("==========");
System.out.println();
System.out.println("KEYSIZE=" + (8 * keysize));
System.out.println("KEY=" + toString(kb));
System.out.println();
}
Object key = makeKey(kb, _BLOCK_SIZE);
if (_RDEBUG && _debuglevel > 6) {
System.out.println("Intermediate Ciphertext Values (Encryption)");
System.out.println();
System.out.println("PT=" + toString(pt));
}
byte[] ct = new byte[_BLOCK_SIZE];
blockEncrypt(pt, ct, 0, 0, key, _BLOCK_SIZE);
if (_RDEBUG && _debuglevel > 6) {
System.out.println("Intermediate Plaintext Values (Decryption)");
System.out.println();
System.out.println("CT=" + toString(ct));
}
byte[] cpt = new byte[_BLOCK_SIZE];
blockDecrypt(ct, cpt, 0, 0, key, _BLOCK_SIZE);
ok = areEqual(pt, cpt);
if (!ok) throw new RuntimeException("Symmetric operation failed");
} catch (Exception x) {
if (_RDEBUG && _debuglevel > 0) {
debug("Exception encountered during self-test: " + x.getMessage());
x.printStackTrace();
}
}
if (_RDEBUG && _debuglevel > 0) debug("Self-test OK? " + ok);
if (_RDEBUG) trace(_OUT, "self_test()");
return ok;
}
/**
* Return The number of rounds for a given Rijndael's key and block sizes.
*
* @param keySize The size of the user key material in bytes.
* @param blockSize The desired block size in bytes.
* @return The number of rounds for a given Rijndael's key and
* block sizes.
*/
public static final int getRounds(int keySize, int blockSize) {
switch (keySize) {
case 16:
return blockSize == 16 ? 10 : (blockSize == 24 ? 12 : 14);
case 24:
return blockSize != 32 ? 12 : 14;
default:
// 32 bytes = 256 bits
return 14;
}
}
// utility static methods (from cryptix.util.core ArrayUtil and Hex classes)
//...........................................................................
/**
* Compares two byte arrays for equality.
*
* @return true if the arrays have identical contents
*/
private static final boolean areEqual(byte[] a, byte[] b) {
int aLength = a.length;
if (aLength != b.length) return false;
for (int i = 0; i < aLength; i++)
if (a[i] != b[i]) return false;
return true;
}
/**
* Returns a string of 2 hexadecimal digits (most significant
* digit first) corresponding to the lowest 8 bits of <i>n</i>.
*/
private static final String byteToString(int n) {
char[] buf = { _HEX_DIGITS[(n >>> 4) & 0x0F], _HEX_DIGITS[n & 0x0F]};
return new String(buf);
}
/**
* Returns a string of 8 hexadecimal digits (most significant
* digit first) corresponding to the integer <i>n</i>, which is
* treated as unsigned.
*/
private static final String intToString(int n) {
char[] buf = new char[8];
for (int i = 7; i >= 0; i--) {
buf[i] = _HEX_DIGITS[n & 0x0F];
n >>>= 4;
}
return new String(buf);
}
/**
* Returns a string of hexadecimal digits from a byte array. Each
* byte is converted to 2 hex symbols.
*/
private static final String toString(byte[] ba) {
int length = ba.length;
char[] buf = new char[length * 2];
for (int i = 0, j = 0, k; i < length;) {
k = ba[i++];
buf[j++] = _HEX_DIGITS[(k >>> 4) & 0x0F];
buf[j++] = _HEX_DIGITS[k & 0x0F];
}
return new String(buf);
}
/**
* Returns a string of hexadecimal digits from an integer array. Each
* int is converted to 4 hex symbols.
*/
private static final String toString(int[] ia) {
int length = ia.length;
char[] buf = new char[length * 8];
for (int i = 0, j = 0, k; i < length; i++) {
k = ia[i];
buf[j++] = _HEX_DIGITS[(k >>> 28) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 24) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 20) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 16) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 12) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 8) & 0x0F];
buf[j++] = _HEX_DIGITS[(k >>> 4) & 0x0F];
buf[j++] = _HEX_DIGITS[k & 0x0F];
}
return new String(buf);
}
// main(): use to generate the Intermediate Values KAT
//...........................................................................
public static void main(String[] args) {
self_test(16);
self_test(24);
self_test(32);
}
}

View File

@ -0,0 +1,66 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* 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 the TheCrypto 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.
*/
import java.math.BigInteger;
import net.i2p.util.NativeBigInteger;
/**
* Primes for ElGamal and DSA from
* http://www.ietf.org/proceedings/03mar/I-D/draft-ietf-ipsec-ike-modp-groups-05.txt
*/
public class CryptoConstants {
public static final BigInteger dsap = new NativeBigInteger(
"9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31"
+ "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f"
+ "f6c3e4548707fef8503d91dd8602e867e6d35d2235c1869ce2479c3b9d5401de04e0727f"
+ "b33d6511285d4cf29538d9e3b6051f5b22cc1c93",
16);
public static final BigInteger dsaq = new NativeBigInteger("a5dfc28fef4ca1e286744cd8eed9d29d684046b7", 16);
public static final BigInteger dsag = new NativeBigInteger(
"c1f4d27d40093b429e962d7223824e0bbc47e7c832a39236fc683af84889581075ff9082"
+ "ed32353d4374d7301cda1d23c431f4698599dda02451824ff369752593647cc3ddc197de"
+ "985e43d136cdcfc6bd5409cd2f450821142a5e6f8eb1c3ab5d0484b8129fcf17bce4f7f3"
+ "3321c3cb3dbb14a905e7b2b3e93be4708cbcc82",
16);
public static final BigInteger elgp = new NativeBigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
public static final BigInteger elgg = new NativeBigInteger("2");
}

View File

@ -0,0 +1,539 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
/**
* Generate a new session key through a diffie hellman exchange. This uses the
* constants defined in CryptoConstants, which causes the exchange to create a
* 256 bit session key.
*
* This class precalcs a set of values on its own thread, using those transparently
* when a new instance is created. By default, the minimum threshold for creating
* new values for the pool is 5, and the max pool size is 10. Whenever the pool has
* less than the minimum, it fills it up again to the max. There is a delay after
* each precalculation so that the CPU isn't hosed during startup (defaulting to 1 second).
* These three parameters are controlled by java environmental variables and
* can be adjusted via:
* -Dcrypto.dh.precalc.min=40 -Dcrypto.dh.precalc.max=100 -Dcrypto.dh.precalc.delay=60000
*
* (delay is milliseconds)
*
* To disable precalculation, set min to 0
*
* @author jrandom
*/
public class DHSessionKeyBuilder {
private static I2PAppContext _context = I2PAppContext.getGlobalContext();
private final static Log _log = new Log(DHSessionKeyBuilder.class);
private static int MIN_NUM_BUILDERS = -1;
private static int MAX_NUM_BUILDERS = -1;
private static int CALC_DELAY = -1;
private static volatile List _builders = new ArrayList(50);
private static Thread _precalcThread = null;
private BigInteger _myPrivateValue;
private BigInteger _myPublicValue;
private BigInteger _peerValue;
private SessionKey _sessionKey;
private ByteArray _extraExchangedBytes; // bytes after the session key from the DH exchange
public final static String PROP_DH_PRECALC_MIN = "crypto.dh.precalc.min";
public final static String PROP_DH_PRECALC_MAX = "crypto.dh.precalc.max";
public final static String PROP_DH_PRECALC_DELAY = "crypto.dh.precalc.delay";
public final static String DEFAULT_DH_PRECALC_MIN = "5";
public final static String DEFAULT_DH_PRECALC_MAX = "50";
public final static String DEFAULT_DH_PRECALC_DELAY = "10000";
static {
I2PAppContext ctx = _context;
ctx.statManager().createRateStat("crypto.dhGeneratePublicTime", "How long it takes to create x and X", "Encryption", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
ctx.statManager().createRateStat("crypto.dhCalculateSessionTime", "How long it takes to create the session key", "Encryption", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
try {
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN));
MIN_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MIN);
MIN_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX));
MAX_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MAX);
MAX_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY));
CALC_DELAY = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_DELAY);
CALC_DELAY = val;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("DH Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: "
+ CALC_DELAY + ")");
_precalcThread = new I2PThread(new DHSessionKeyBuilderPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS));
_precalcThread.setName("DH Precalc");
_precalcThread.setDaemon(true);
_precalcThread.setPriority(Thread.MIN_PRIORITY);
_precalcThread.start();
}
/**
* Construct a new DH key builder
*
*/
public DHSessionKeyBuilder() {
this(false);
DHSessionKeyBuilder builder = null;
synchronized (_builders) {
if (_builders.size() > 0) {
builder = (DHSessionKeyBuilder) _builders.remove(0);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Removing a builder. # left = " + _builders.size());
} else {
if (_log.shouldLog(Log.WARN)) _log.warn("NO MORE BUILDERS! creating one now");
}
}
if (builder != null) {
_myPrivateValue = builder._myPrivateValue;
_myPublicValue = builder._myPublicValue;
_peerValue = builder._peerValue;
_sessionKey = builder._sessionKey;
_extraExchangedBytes = builder._extraExchangedBytes;
} else {
_myPrivateValue = null;
_myPublicValue = null;
_peerValue = null;
_sessionKey = null;
_myPublicValue = generateMyValue();
_extraExchangedBytes = new ByteArray();
}
}
public DHSessionKeyBuilder(boolean usePool) {
_myPrivateValue = null;
_myPublicValue = null;
_peerValue = null;
_sessionKey = null;
_extraExchangedBytes = new ByteArray();
}
/**
* Conduct a DH exchange over the streams, returning the resulting data.
*
* @return exchanged data
* @throws IOException if there is an error (but does not close the streams
*/
public static DHSessionKeyBuilder exchangeKeys(InputStream in, OutputStream out) throws IOException {
DHSessionKeyBuilder builder = new DHSessionKeyBuilder();
// send: X
writeBigI(out, builder.getMyPublicValue());
// read: Y
BigInteger Y = readBigI(in);
if (Y == null) return null;
try {
builder.setPeerPublicValue(Y);
return builder;
} catch (InvalidPublicParameterException ippe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Key exchange failed (hostile peer?)", ippe);
return null;
}
}
static BigInteger readBigI(InputStream in) throws IOException {
byte Y[] = new byte[256];
int read = DataHelper.read(in, Y);
if (read != 256) {
return null;
}
if (1 == (Y[0] & 0x80)) {
// high bit set, need to inject an additional byte to keep 2s complement
if (_log.shouldLog(Log.DEBUG))
_log.debug("High bit set");
byte Y2[] = new byte[257];
System.arraycopy(Y, 0, Y2, 1, 256);
Y = Y2;
}
return new NativeBigInteger(1, Y);
}
/**
* Write out the integer as a 256 byte value. This left pads with 0s so
* to keep in 2s complement, and if it is already 257 bytes (due to
* the sign bit) ignore that first byte.
*/
static void writeBigI(OutputStream out, BigInteger val) throws IOException {
byte x[] = val.toByteArray();
for (int i = x.length; i < 256; i++)
out.write(0);
if (x.length == 257)
out.write(x, 1, 256);
else if (x.length == 256)
out.write(x);
else if (x.length > 257)
throw new IllegalArgumentException("Value is too large! length="+x.length);
out.flush();
}
private static final int getSize() {
synchronized (_builders) {
return _builders.size();
}
}
private static final int addBuilder(DHSessionKeyBuilder builder) {
int sz = 0;
synchronized (_builders) {
_builders.add(builder);
sz = _builders.size();
}
return sz;
}
/**
* Create a new private value for the DH exchange, and return the number to
* be exchanged, leaving the actual private value accessible through getMyPrivateValue()
*
*/
public BigInteger generateMyValue() {
long start = System.currentTimeMillis();
_myPrivateValue = new NativeBigInteger(KeyGenerator.PUBKEY_EXPONENT_SIZE, RandomSource.getInstance());
BigInteger myValue = CryptoConstants.elgg.modPow(_myPrivateValue, CryptoConstants.elgp);
long end = System.currentTimeMillis();
long diff = end - start;
_context.statManager().addRateData("crypto.dhGeneratePublicTime", diff, diff);
if (diff > 1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Took more than a second (" + diff + "ms) to generate local DH value");
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Took " + diff + "ms to generate local DH value");
}
return myValue;
}
/**
* Retrieve the private value used by the local participant in the DH exchange
*/
public BigInteger getMyPrivateValue() {
return _myPrivateValue;
}
/**
* Retrieve the public value used by the local participant in the DH exchange,
* generating it if necessary
*/
public BigInteger getMyPublicValue() {
if (_myPublicValue == null) _myPublicValue = generateMyValue();
return _myPublicValue;
}
/**
* Return a 256 byte representation of our public key, with leading 0s
* if necessary.
*
*/
public byte[] getMyPublicValueBytes() {
return toByteArray(getMyPublicValue());
}
private static final byte[] toByteArray(BigInteger bi) {
byte data[] = bi.toByteArray();
byte rv[] = new byte[256];
if (data.length == 257) // high byte has the sign bit
System.arraycopy(data, 1, rv, 0, rv.length);
else if (data.length == 256)
System.arraycopy(data, 0, rv, 0, rv.length);
else
System.arraycopy(data, 0, rv, rv.length-data.length, data.length);
return rv;
}
/**
* Specify the value given by the peer for use in the session key negotiation
*
*/
public void setPeerPublicValue(BigInteger peerVal) throws InvalidPublicParameterException {
validatePublic(peerVal);
_peerValue = peerVal;
}
public void setPeerPublicValue(byte val[]) throws InvalidPublicParameterException {
if (val.length != 256)
throw new IllegalArgumentException("Peer public value must be exactly 256 bytes");
if (1 == (val[0] & 0x80)) {
// high bit set, need to inject an additional byte to keep 2s complement
if (_log.shouldLog(Log.DEBUG))
_log.debug("High bit set");
byte val2[] = new byte[257];
System.arraycopy(val, 0, val2, 1, 256);
val = val2;
}
setPeerPublicValue(new NativeBigInteger(1, val));
//_peerValue = new NativeBigInteger(val);
}
public BigInteger getPeerPublicValue() {
return _peerValue;
}
public byte[] getPeerPublicValueBytes() {
return toByteArray(getPeerPublicValue());
}
/**
* Retrieve the session key, calculating it if necessary (and if possible).
*
* @return session key exchanged, or null if the exchange is not complete
*/
public SessionKey getSessionKey() {
if (_sessionKey != null) return _sessionKey;
if (_peerValue != null) {
if (_myPrivateValue == null) generateMyValue();
_sessionKey = calculateSessionKey(_myPrivateValue, _peerValue);
} else {
//System.err.println("Not ready yet.. privateValue and peerValue must be set ("
// + (_myPrivateValue != null ? "set" : "null") + ","
// + (_peerValue != null ? "set" : "null") + ")");
}
return _sessionKey;
}
/**
* Retrieve the extra bytes beyond the session key resulting from the DH exchange.
* If there aren't enough bytes (with all of them being consumed by the 32 byte key),
* the SHA256 of the key itself is used.
*
*/
public ByteArray getExtraBytes() {
return _extraExchangedBytes;
}
/**
* Calculate a session key based on the private value and the public peer value
*
*/
private final SessionKey calculateSessionKey(BigInteger myPrivateValue, BigInteger publicPeerValue) {
long start = System.currentTimeMillis();
SessionKey key = new SessionKey();
BigInteger exchangedKey = publicPeerValue.modPow(myPrivateValue, CryptoConstants.elgp);
byte buf[] = exchangedKey.toByteArray();
byte val[] = new byte[32];
if (buf.length < val.length) {
System.arraycopy(buf, 0, val, 0, buf.length);
byte remaining[] = SHA256Generator.getInstance().calculateHash(val).getData();
_extraExchangedBytes.setData(remaining);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Storing " + remaining.length + " bytes from the DH exchange by SHA256 the session key");
} else { // (buf.length >= val.length)
System.arraycopy(buf, 0, val, 0, val.length);
// feed the extra bytes into the PRNG
_context.random().harvester().feedEntropy("DH", buf, val.length, buf.length-val.length);
byte remaining[] = new byte[buf.length - val.length];
System.arraycopy(buf, val.length, remaining, 0, remaining.length);
_extraExchangedBytes.setData(remaining);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Storing " + remaining.length + " bytes from the end of the DH exchange");
}
key.setData(val);
long end = System.currentTimeMillis();
long diff = end - start;
_context.statManager().addRateData("crypto.dhCalculateSessionTime", diff, diff);
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Generating session key took too long (" + diff + " ms");
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Generating session key " + diff + " ms");
}
return key;
}
/**
* rfc2631:
* The following algorithm MAY be used to validate a received public key y.
*
* 1. Verify that y lies within the interval [2,p-1]. If it does not,
* the key is invalid.
* 2. Compute y^q mod p. If the result == 1, the key is valid.
* Otherwise the key is invalid.
*/
private static final void validatePublic(BigInteger publicValue) throws InvalidPublicParameterException {
int cmp = publicValue.compareTo(NativeBigInteger.ONE);
if (cmp <= 0)
throw new InvalidPublicParameterException("Public value is below two: " + publicValue.toString());
cmp = publicValue.compareTo(CryptoConstants.elgp);
if (cmp >= 0)
throw new InvalidPublicParameterException("Public value is above p-1: " + publicValue.toString());
// todo:
// whatever validation needs to be done to mirror the rfc's part 2 (we don't have a q, so can't do
// if (NativeBigInteger.ONE.compareTo(publicValue.modPow(q, CryptoConstants.elgp)) != 0)
// throw new InvalidPublicParameterException("Invalid public value with y^q mod p != 1");
//
}
/*
private static void testValidation() {
NativeBigInteger bi = new NativeBigInteger("-3416069082912684797963255430346582466254460710249795973742848334283491150671563023437888953432878859472362439146158925287289114133666004165938814597775594104058593692562989626922979416277152479694258099203456493995467386903611666213773085025718340335205240293383622352894862685806192183268523899615405287022135356656720938278415659792084974076416864813957028335830794117802560169423133816961503981757298122040391506600117301607823659479051969827845787626261515313227076880722069706394405554113103165334903531980102626092646197079218895216346725765704256096661045699444128316078549709132753443706200863682650825635513");
try {
validatePublic(bi);
System.err.println("valid?!");
} catch (InvalidPublicParameterException ippe) {
System.err.println("Ok, invalid. cool");
}
byte val[] = bi.toByteArray();
System.out.println("Len: " + val.length + " first is ok? " + ( (val[0] & 0x80) == 1)
+ "\n" + DataHelper.toString(val, 64));
NativeBigInteger bi2 = new NativeBigInteger(1, val);
try {
validatePublic(bi2);
System.out.println("valid");
} catch (InvalidPublicParameterException ippe) {
System.out.println("invalid");
}
}
*/
public static void main(String args[]) {
//if (true) { testValidation(); return; }
RandomSource.getInstance().nextBoolean(); // warm it up
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException ie) { // nop
}
I2PAppContext ctx = new I2PAppContext();
_log.debug("\n\n\n\nBegin test\n");
long negTime = 0;
try {
for (int i = 0; i < 5; i++) {
long startNeg = Clock.getInstance().now();
DHSessionKeyBuilder builder1 = new DHSessionKeyBuilder();
DHSessionKeyBuilder builder2 = new DHSessionKeyBuilder();
BigInteger pub1 = builder1.getMyPublicValue();
builder2.setPeerPublicValue(pub1);
BigInteger pub2 = builder2.getMyPublicValue();
builder1.setPeerPublicValue(pub2);
SessionKey key1 = builder1.getSessionKey();
SessionKey key2 = builder2.getSessionKey();
long endNeg = Clock.getInstance().now();
negTime += endNeg - startNeg;
if (!key1.equals(key2))
_log.error("**ERROR: Keys do not match");
else
_log.debug("**Success: Keys match");
byte iv[] = new byte[16];
RandomSource.getInstance().nextBytes(iv);
String origVal = "1234567890123456"; // 16 bytes max using AESEngine
byte enc[] = new byte[16];
byte dec[] = new byte[16];
ctx.aes().encrypt(origVal.getBytes(), 0, enc, 0, key1, iv, 16);
ctx.aes().decrypt(enc, 0, dec, 0, key2, iv, 16);
String tranVal = new String(dec);
if (origVal.equals(tranVal))
_log.debug("**Success: D(E(val)) == val");
else
_log.error("**ERROR: D(E(val)) != val [val=(" + tranVal + "), origVal=(" + origVal + ")");
}
} catch (InvalidPublicParameterException ippe) {
_log.error("Invalid dh", ippe);
}
_log.debug("Negotiation time for 5 runs: " + negTime + " @ " + negTime / 5l + "ms each");
try {
Thread.sleep(2000);
} catch (InterruptedException ie) { // nop
}
}
private static class DHSessionKeyBuilderPrecalcRunner implements Runnable {
private int _minSize;
private int _maxSize;
private DHSessionKeyBuilderPrecalcRunner(int minSize, int maxSize) {
_minSize = minSize;
_maxSize = maxSize;
}
public void run() {
while (true) {
int curSize = 0;
long start = Clock.getInstance().now();
int startSize = getSize();
curSize = startSize;
while (curSize < _minSize) {
while (curSize < _maxSize) {
long curStart = System.currentTimeMillis();
curSize = addBuilder(precalc(curSize));
long curCalc = System.currentTimeMillis() - curStart;
// for some relief...
try {
Thread.sleep(CALC_DELAY + curCalc * 10);
} catch (InterruptedException ie) { // nop
}
}
}
long end = Clock.getInstance().now();
int numCalc = curSize - startSize;
if (numCalc > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Precalced " + numCalc + " to " + curSize + " in "
+ (end - start - CALC_DELAY * numCalc) + "ms (not counting "
+ (CALC_DELAY * numCalc) + "ms relief). now sleeping");
}
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException ie) { // nop
}
}
}
private DHSessionKeyBuilder precalc(int i) {
DHSessionKeyBuilder builder = new DHSessionKeyBuilder(false);
builder.getMyPublicValue();
//_log.debug("Precalc " + i + " complete");
return builder;
}
}
public static class InvalidPublicParameterException extends I2PException {
public InvalidPublicParameterException() {
super();
}
public InvalidPublicParameterException(String msg) {
super(msg);
}
}
}

View File

@ -0,0 +1,228 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* 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 the TheCrypto 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.
*/
import java.io.InputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
public class DSAEngine {
private Log _log;
private I2PAppContext _context;
public DSAEngine(I2PAppContext context) {
_log = context.logManager().getLog(DSAEngine.class);
_context = context;
}
public static DSAEngine getInstance() {
return I2PAppContext.getGlobalContext().dsa();
}
public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
return verifySignature(signature, signedData, 0, signedData.length, verifyingKey);
}
public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) {
return verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey);
}
public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) {
return verifySignature(signature, calculateHash(in), verifyingKey);
}
public boolean verifySignature(Signature signature, Hash hash, SigningPublicKey verifyingKey) {
long start = _context.clock().now();
try {
byte[] sigbytes = signature.getData();
byte rbytes[] = new byte[20];
byte sbytes[] = new byte[20];
for (int x = 0; x < 40; x++) {
if (x < 20) {
rbytes[x] = sigbytes[x];
} else {
sbytes[x - 20] = sigbytes[x];
}
}
BigInteger s = new NativeBigInteger(1, sbytes);
BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = null;
try {
w = s.modInverse(CryptoConstants.dsaq);
} catch (ArithmeticException ae) {
return false;
}
byte data[] = hash.getData();
NativeBigInteger bi = new NativeBigInteger(1, data);
BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
BigInteger modval = CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap);
BigInteger modmulval = modval.multiply(y.modPow(u2,CryptoConstants.dsap));
BigInteger v = (modmulval).mod(CryptoConstants.dsap).mod(CryptoConstants.dsaq);
boolean ok = v.compareTo(r) == 0;
long diff = _context.clock().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Took too long to verify the signature (" + diff + "ms)");
}
return ok;
} catch (Exception e) {
_log.log(Log.CRIT, "Error verifying the signature", e);
return false;
}
}
public Signature sign(byte data[], SigningPrivateKey signingKey) {
return sign(data, 0, data.length, signingKey);
}
public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) {
if ((signingKey == null) || (data == null) || (data.length <= 0)) return null;
Hash h = calculateHash(data, offset, length);
return sign(h, signingKey);
}
public Signature sign(InputStream in, SigningPrivateKey signingKey) {
if ((signingKey == null) || (in == null) ) return null;
Hash h = calculateHash(in);
return sign(h, signingKey);
}
public Signature sign(Hash hash, SigningPrivateKey signingKey) {
if ((signingKey == null) || (hash == null)) return null;
long start = _context.clock().now();
Signature sig = new Signature();
BigInteger k;
boolean ok = false;
do {
k = new BigInteger(160, _context.random());
ok = k.compareTo(CryptoConstants.dsaq) != 1;
ok = ok && !k.equals(BigInteger.ZERO);
//System.out.println("K picked (ok? " + ok + "): " + k.bitLength() + ": " + k.toString());
} while (!ok);
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
BigInteger kinv = k.modInverse(CryptoConstants.dsaq);
BigInteger M = new NativeBigInteger(1, hash.getData());
BigInteger x = new NativeBigInteger(1, signingKey.getData());
BigInteger s = (kinv.multiply(M.add(x.multiply(r)))).mod(CryptoConstants.dsaq);
byte[] rbytes = r.toByteArray();
byte[] sbytes = s.toByteArray();
byte[] out = new byte[40];
// (q^random)%p is computationally random
_context.random().harvester().feedEntropy("DSA.sign", rbytes, 0, rbytes.length);
if (rbytes.length == 20) {
for (int i = 0; i < 20; i++) {
out[i] = rbytes[i];
}
} else if (rbytes.length == 21) {
for (int i = 0; i < 20; i++) {
out[i] = rbytes[i + 1];
}
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short rbytes.length [" + rbytes.length + "]");
for (int i = 0; i < rbytes.length; i++)
out[i + 20 - rbytes.length] = rbytes[i];
}
if (sbytes.length == 20) {
for (int i = 0; i < 20; i++) {
out[i + 20] = sbytes[i];
}
} else if (sbytes.length == 21) {
for (int i = 0; i < 20; i++) {
out[i + 20] = sbytes[i + 1];
}
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short sbytes.length [" + sbytes.length + "]");
for (int i = 0; i < sbytes.length; i++)
out[i + 20 + 20 - sbytes.length] = sbytes[i];
}
sig.setData(out);
long diff = _context.clock().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to sign (" + diff + "ms)");
}
return sig;
}
public Hash calculateHash(InputStream in) {
SHA1 digest = new SHA1();
byte buf[] = new byte[64];
int read = 0;
try {
while ( (read = in.read(buf)) != -1) {
digest.engineUpdate(buf, 0, read);
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to hash the stream", ioe);
return null;
}
return new Hash(digest.engineDigest());
}
public static Hash calculateHash(byte[] source, int offset, int len) {
SHA1 h = new SHA1();
h.engineUpdate(source, offset, len);
byte digested[] = h.digest();
return new Hash(digested);
}
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte data[] = new byte[4096];
ctx.random().nextBytes(data);
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
try {
for (int i = 0; i < 10; i++) {
Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]);
boolean ok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]);
System.out.println("OK: " + ok);
}
} catch (Exception e) { e.printStackTrace(); }
ctx.random().saveSeed();
}
}

View File

@ -0,0 +1,26 @@
package net.i2p.crypto;
import net.i2p.I2PAppContext;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
/**
* Stub that offers no authentication.
*
*/
public class DummyDSAEngine extends DSAEngine {
public DummyDSAEngine(I2PAppContext context) {
super(context);
}
public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
return true;
}
public Signature sign(byte data[], SigningPrivateKey signingKey) {
Signature sig = new Signature();
sig.setData(Signature.FAKE_SIGNATURE);
return sig;
}
}

View File

@ -0,0 +1,106 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.util.Log;
/**
* Fake ElG E and D, useful for when performance isn't being tested
*
* @author jrandom
*/
public class DummyElGamalEngine extends ElGamalEngine {
private Log _log;
/**
* The ElGamal engine should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public DummyElGamalEngine(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(DummyElGamalEngine.class);
_log.log(Log.CRIT, "Dummy ElGamal engine in use! NO DATA SECURITY. Danger Will Robinson, Danger!",
new Exception("I really hope you know what you're doing"));
}
private DummyElGamalEngine() { super(null); }
/** encrypt the data to the public key
* @return encrypted data
* @param publicKey public key encrypt to
* @param data data to encrypt
*/
public byte[] encrypt(byte data[], PublicKey publicKey) {
if ((data == null) || (data.length >= 223))
throw new IllegalArgumentException("Data to encrypt must be < 223 bytes at the moment");
if (publicKey == null) throw new IllegalArgumentException("Null public key specified");
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
try {
baos.write(0xFF);
Hash hash = SHA256Generator.getInstance().calculateHash(data);
hash.writeBytes(baos);
baos.write(data);
baos.flush();
} catch (Exception e) {
_log.error("Internal error writing to buffer", e);
return null;
}
byte d2[] = baos.toByteArray();
byte[] out = new byte[514];
System.arraycopy(d2, 0, out, (d2.length < 257 ? 257 - d2.length : 0), (d2.length > 257 ? 257 : d2.length));
return out;
}
/** Decrypt the data
* @param encrypted encrypted data
* @param privateKey private key to decrypt with
* @return unencrypted data
*/
public byte[] decrypt(byte encrypted[], PrivateKey privateKey) {
if ((encrypted == null) || (encrypted.length > 514))
throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment");
byte val[] = new byte[257];
System.arraycopy(encrypted, 0, val, 0, val.length);
int i = 0;
for (i = 0; i < val.length; i++)
if (val[i] != (byte) 0x00) break;
ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i);
Hash hash = new Hash();
byte rv[] = null;
try {
bais.read(); // skip first byte
hash.readBytes(bais);
rv = new byte[val.length - i - 1 - 32];
bais.read(rv);
} catch (Exception e) {
_log.error("Internal error reading value", e);
return null;
}
Hash calcHash = SHA256Generator.getInstance().calculateHash(rv);
if (calcHash.equals(hash)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
return rv;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = " + new String(rv),
new Exception("Doesn't match"));
return null;
}
}

View File

@ -0,0 +1,98 @@
package net.i2p.crypto;
import java.util.Random;
import net.i2p.I2PAppContext;
import net.i2p.util.PooledRandomSource;
import net.i2p.util.RandomSource;
import net.i2p.util.Log;
/**
*
*/
public class DummyPooledRandomSource extends PooledRandomSource {
public DummyPooledRandomSource(I2PAppContext context) {
super(context);
}
protected void initializePool(I2PAppContext context) {
_pool = new RandomSource[POOL_SIZE];
for (int i = 0; i < POOL_SIZE; i++) {
_pool[i] = new DummyRandomSource(context);
_pool[i].nextBoolean();
}
_nextPool = 0;
}
private class DummyRandomSource extends RandomSource {
private Random _prng;
public DummyRandomSource(I2PAppContext context) {
super(context);
// when we replace to have hooks for fortuna (etc), replace with
// a factory (or just a factory method)
_prng = new Random();
}
/**
* According to the java docs (http://java.sun.com/j2se/1.4.1/docs/api/java/util/Random.html#nextInt(int))
* nextInt(n) should return a number between 0 and n (including 0 and excluding n). However, their pseudocode,
* as well as sun's, kaffe's, and classpath's implementation INCLUDES NEGATIVE VALUES.
* WTF. Ok, so we're going to have it return between 0 and n (including 0, excluding n), since
* thats what it has been used for.
*
*/
public int nextInt(int n) {
if (n == 0) return 0;
int val = _prng.nextInt(n);
if (val < 0) val = 0 - val;
if (val >= n) val = val % n;
return val;
}
/**
* Like the modified nextInt, nextLong(n) returns a random number from 0 through n,
* including 0, excluding n.
*/
public long nextLong(long n) {
long v = _prng.nextLong();
if (v < 0) v = 0 - v;
if (v >= n) v = v % n;
return v;
}
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public boolean nextBoolean() { return _prng.nextBoolean(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public void nextBytes(byte buf[]) { _prng.nextBytes(buf); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public double nextDouble() { return _prng.nextDouble(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public float nextFloat() { return _prng.nextFloat(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public double nextGaussian() { return _prng.nextGaussian(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public int nextInt() { return _prng.nextInt(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public long nextLong() { return _prng.nextLong(); }
}
}

View File

@ -0,0 +1,615 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.util.Log;
/**
* Handles the actual ElGamal+AES encryption and decryption scenarios using the
* supplied keys and data.
*/
public class ElGamalAESEngine {
private final static Log _log = new Log(ElGamalAESEngine.class);
private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size
private I2PAppContext _context;
private ElGamalAESEngine() { // nop
}
public ElGamalAESEngine(I2PAppContext ctx) {
_context = ctx;
_context.statManager().createFrequencyStat("crypto.elGamalAES.encryptNewSession",
"how frequently we encrypt to a new ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.encryptExistingSession",
"how frequently we encrypt to an existing ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptNewSession",
"how frequently we decrypt with a new ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptExistingSession",
"how frequently we decrypt with an existing ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptFailed",
"how frequently we fail to decrypt with ElGamal/AES+SessionTag?", "Encryption",
new long[] { 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
}
/**
* Decrypt the message using the given private key. This works according to the
* ElGamal+AES algorithm in the data structure spec.
*
*/
public byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
if (data == null) {
if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?");
return null;
} else if (data.length < MIN_ENCRYPTED_SIZE) {
if (_log.shouldLog(Log.ERROR))
_log.error("Data is less than the minimum size (" + data.length + " < " + MIN_ENCRYPTED_SIZE + ")");
return null;
}
byte tag[] = new byte[32];
System.arraycopy(data, 0, tag, 0, tag.length);
SessionTag st = new SessionTag(tag);
SessionKey key = _context.sessionKeyManager().consumeTag(st);
SessionKey foundKey = new SessionKey();
foundKey.setData(null);
SessionKey usedKey = new SessionKey();
Set foundTags = new HashSet();
byte decrypted[] = null;
boolean wasExisting = false;
if (key != null) {
//if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is known for tag " + st);
usedKey.setData(key.getData());
long id = _context.random().nextLong();
if (_log.shouldLog(Log.DEBUG))
_log.debug(id + ": Decrypting existing session encrypted with tag: " + st.toString() + ": key: " + key.toBase64() + ": " + data.length + " bytes: " + Base64.encode(data, 0, 64));
decrypted = decryptExistingSession(data, key, targetPrivateKey, foundTags, usedKey, foundKey);
if (decrypted != null) {
_context.statManager().updateFrequency("crypto.elGamalAES.decryptExistingSession");
if ( (foundTags.size() > 0) && (_log.shouldLog(Log.WARN)) )
_log.warn(id + ": ElG/AES decrypt success with " + st + ": found tags: " + foundTags);
wasExisting = true;
} else {
_context.statManager().updateFrequency("crypto.elGamalAES.decryptFailed");
if (_log.shouldLog(Log.WARN)) {
_log.warn(id + ": ElG decrypt fail: known tag [" + st + "], failed decrypt");
}
}
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is NOT known for tag " + st);
decrypted = decryptNewSession(data, targetPrivateKey, foundTags, usedKey, foundKey);
if (decrypted != null) {
_context.statManager().updateFrequency("crypto.elGamalAES.decryptNewSession");
if ( (foundTags.size() > 0) && (_log.shouldLog(Log.WARN)) )
_log.warn("ElG decrypt success: found tags: " + foundTags);
} else {
_context.statManager().updateFrequency("crypto.elGamalAES.decryptFailed");
if (_log.shouldLog(Log.WARN))
_log.warn("ElG decrypt fail: unknown tag: " + st);
}
}
if ((key == null) && (decrypted == null)) {
//_log.debug("Unable to decrypt the data starting with tag [" + st + "] - did the tag expire recently?", new Exception("Decrypt failure"));
}
if (foundTags.size() > 0) {
if (foundKey.getData() != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Found key: " + foundKey.toBase64() + " tags: " + foundTags + " wasExisting? " + wasExisting);
_context.sessionKeyManager().tagsReceived(foundKey, foundTags);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Used key: " + usedKey.toBase64() + " tags: " + foundTags + " wasExisting? " + wasExisting);
_context.sessionKeyManager().tagsReceived(usedKey, foundTags);
}
}
return decrypted;
}
/**
* scenario 1:
* Begin with 222 bytes, ElG encrypted, containing:
* - 32 byte SessionKey
* - 32 byte pre-IV for the AES
* - 158 bytes of random padding
* Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV, using
* the decryptAESBlock method & structure.
*
* @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*
* @return null if decryption fails
*/
byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey,
SessionKey foundKey) throws DataFormatException {
if (data == null) {
//if (_log.shouldLog(Log.WARN)) _log.warn("Data is null, unable to decrypt new session");
return null;
} else if (data.length < 514) {
//if (_log.shouldLog(Log.WARN)) _log.warn("Data length is too small (" + data.length + ")");
return null;
}
byte elgEncr[] = new byte[514];
if (data.length > 514) {
System.arraycopy(data, 0, elgEncr, 0, 514);
} else {
System.arraycopy(data, 0, elgEncr, 514 - data.length, data.length);
}
byte elgDecr[] = _context.elGamalEngine().decrypt(elgEncr, targetPrivateKey);
if (elgDecr == null) {
//if (_log.shouldLog(Log.WARN))
// _log.warn("decrypt returned null", new Exception("decrypt failed"));
return null;
}
byte preIV[] = null;
int offset = 0;
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
System.arraycopy(elgDecr, offset, key, 0, SessionKey.KEYSIZE_BYTES);
offset += SessionKey.KEYSIZE_BYTES;
usedKey.setData(key);
preIV = new byte[32];
System.arraycopy(elgDecr, offset, preIV, 0, 32);
offset += 32;
//_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
// feed the extra bytes into the PRNG
_context.random().harvester().feedEntropy("ElG/AES", elgDecr, offset, elgDecr.length - offset);
byte aesDecr[] = decryptAESBlock(data, 514, data.length-514, usedKey, iv, null, foundTags, foundKey);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Decrypt with a NEW session successfull: # tags read = " + foundTags.size(),
// new Exception("Decrypted by"));
return aesDecr;
}
/**
* scenario 2:
* The data begins with 32 byte session tag, which also serves as the preIV.
* Then decrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
* If anything doesn't match up in decryption, it falls back to decryptNewSession
*
* @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*
*/
byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
byte preIV[] = new byte[32];
System.arraycopy(data, 0, preIV, 0, preIV.length);
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
usedKey.setData(key.getData());
//_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
byte decrypted[] = decryptAESBlock(data, 32, data.length-32, key, iv, preIV, foundTags, foundKey);
if (decrypted == null) {
// it begins with a valid session tag, but thats just a coincidence.
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Decrypt with a non session tag, but tags read: " + foundTags.size());
if (_log.shouldLog(Log.WARN))
_log.warn("Decrypting looks negative... existing key fails with existing tag, lets try as a new one");
byte rv[] = decryptNewSession(data, targetPrivateKey, foundTags, usedKey, foundKey);
if (_log.shouldLog(Log.WARN)) {
if (rv == null)
_log.warn("Decrypting failed with a known existing tag as either an existing message or a new session");
else
_log.warn("Decrypting suceeded as a new session, even though it used an existing tag!");
}
return rv;
}
// existing session decrypted successfully!
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Decrypt with an EXISTING session tag successfull, # tags read: " + foundTags.size(),
// new Exception("Decrypted by"));
return decrypted;
}
/**
* Decrypt the AES data with the session key and IV. The result should be:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
* If anything doesn't match up in decryption, return null. Otherwise, return
* the decrypted data and update the session as necessary. If the sentTag is not null,
* consume it, but if it is null, record the keys, etc as part of a new session.
*
* @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*/
byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[],
byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
return decryptAESBlock(encrypted, 0, encrypted.length, key, iv, sentTag, foundTags, foundKey);
}
byte[] decryptAESBlock(byte encrypted[], int offset, int encryptedLen, SessionKey key, byte iv[],
byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
//_log.debug("iv for decryption: " + DataHelper.toString(iv, 16));
//_log.debug("decrypting AES block. encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32));
byte decrypted[] = new byte[encryptedLen];
_context.aes().decrypt(encrypted, offset, decrypted, 0, key, iv, encryptedLen);
//Hash h = _context.sha().calculateHash(decrypted);
//_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
try {
SessionKey newKey = null;
Hash readHash = null;
List tags = null;
//ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);
int cur = 0;
long numTags = DataHelper.fromLong(decrypted, cur, 2);
if (numTags > 0) tags = new ArrayList((int)numTags);
cur += 2;
//_log.debug("# tags: " + numTags);
if ((numTags < 0) || (numTags > 200)) throw new Exception("Invalid number of session tags");
if (numTags * SessionTag.BYTE_LENGTH > decrypted.length - 2) {
throw new Exception("# tags: " + numTags + " is too many for " + (decrypted.length - 2));
}
for (int i = 0; i < numTags; i++) {
byte tag[] = new byte[SessionTag.BYTE_LENGTH];
System.arraycopy(decrypted, cur, tag, 0, SessionTag.BYTE_LENGTH);
cur += SessionTag.BYTE_LENGTH;
tags.add(new SessionTag(tag));
}
long len = DataHelper.fromLong(decrypted, cur, 4);
cur += 4;
//_log.debug("len: " + len);
if ((len < 0) || (len > decrypted.length - cur - Hash.HASH_LENGTH - 1))
throw new Exception("Invalid size of payload (" + len + ", remaining " + (decrypted.length-cur) +")");
byte hashval[] = new byte[Hash.HASH_LENGTH];
System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
readHash = new Hash();
readHash.setData(hashval);
byte flag = decrypted[cur++];
if (flag == 0x01) {
byte rekeyVal[] = new byte[SessionKey.KEYSIZE_BYTES];
System.arraycopy(decrypted, cur, rekeyVal, 0, SessionKey.KEYSIZE_BYTES);
cur += SessionKey.KEYSIZE_BYTES;
newKey = new SessionKey();
newKey.setData(rekeyVal);
}
byte unencrData[] = new byte[(int) len];
System.arraycopy(decrypted, cur, unencrData, 0, (int)len);
cur += len;
Hash calcHash = _context.sha().calculateHash(unencrData);
boolean eq = calcHash.equals(readHash);
if (eq) {
// everything matches. w00t.
if (tags != null)
foundTags.addAll(tags);
if (newKey != null) foundKey.setData(newKey.getData());
return unencrData;
}
throw new Exception("Hash does not match");
} catch (Exception e) {
if (_log.shouldLog(Log.WARN)) _log.warn("Unable to decrypt AES block", e);
return null;
}
}
/**
* Encrypt the unencrypted data to the target. The total size returned will be
* no less than the paddedSize parameter, but may be more. This method uses the
* ElGamal+AES algorithm in the data structure spec.
*
* @param target public key to which the data should be encrypted.
* @param key session key to use during encryption
* @param tagsForDelivery session tags to be associated with the key (or newKey if specified), or null
* @param currentTag sessionTag to use, or null if it should use ElG
* @param newKey key to be delivered to the target, with which the tagsForDelivery should be associated
* @param paddedSize minimum size in bytes of the body after padding it (if less than the
* body's real size, no bytes are appended but the body is not truncated)
*/
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionTag currentTag, SessionKey newKey, long paddedSize) {
if (currentTag == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Current tag is null, encrypting as new session", new Exception("encrypt new"));
_context.statManager().updateFrequency("crypto.elGamalAES.encryptNewSession");
return encryptNewSession(data, target, key, tagsForDelivery, newKey, paddedSize);
}
if (_log.shouldLog(Log.INFO))
_log.info("Current tag is NOT null, encrypting as existing session", new Exception("encrypt existing"));
_context.statManager().updateFrequency("crypto.elGamalAES.encryptExistingSession");
byte rv[] = encryptExistingSession(data, target, key, tagsForDelivery, currentTag, newKey, paddedSize);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Existing session encrypted with tag: " + currentTag.toString() + ": " + rv.length + " bytes and key: " + key.toBase64() + ": " + Base64.encode(rv, 0, 64));
return rv;
}
/**
* Encrypt the data to the target using the given key and deliver the specified tags
*/
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionTag currentTag, long paddedSize) {
return encrypt(data, target, key, tagsForDelivery, currentTag, null, paddedSize);
}
/**
* Encrypt the data to the target using the given key and deliver the specified tags
*/
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) {
return encrypt(data, target, key, tagsForDelivery, null, null, paddedSize);
}
/**
* Encrypt the data to the target using the given key delivering no tags
*/
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) {
return encrypt(data, target, key, null, null, null, paddedSize);
}
/**
* scenario 1:
* Begin with 222 bytes, ElG encrypted, containing:
* - 32 byte SessionKey
* - 32 byte pre-IV for the AES
* - 158 bytes of random padding
* Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionKey newKey, long paddedSize) {
//_log.debug("Encrypting to a NEW session");
byte elgSrcData[] = new byte[SessionKey.KEYSIZE_BYTES+32+158];
System.arraycopy(key.getData(), 0, elgSrcData, 0, SessionKey.KEYSIZE_BYTES);
byte preIV[] = new byte[32];
_context.random().nextBytes(preIV);
System.arraycopy(preIV, 0, elgSrcData, SessionKey.KEYSIZE_BYTES, 32);
byte rnd[] = new byte[158];
_context.random().nextBytes(rnd);
System.arraycopy(rnd, 0, elgSrcData, SessionKey.KEYSIZE_BYTES+32, 158);
//_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
long before = _context.clock().now();
byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrcData, target);
long after = _context.clock().now();
if (_log.shouldLog(Log.INFO))
_log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms");
if (elgEncr.length < 514) {
byte elg[] = new byte[514];
int diff = elg.length - elgEncr.length;
//if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff);
System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length);
elgEncr = elg;
}
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
// should we also feed the encrypted elG block into the harvester?
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
//_log.debug("AES encrypted length: " + aesEncr.length);
byte rv[] = new byte[elgEncr.length + aesEncr.length];
System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length);
System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length);
//_log.debug("Return length: " + rv.length);
long finish = _context.clock().now();
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms");
return rv;
}
/**
* scenario 2:
* Begin with 32 byte session tag, which also serves as the preIV.
* Then encrypt with AES using that session key and the first 16 bytes of the SHA256 of the pre-IV:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionTag currentTag, SessionKey newKey, long paddedSize) {
//_log.debug("Encrypting to an EXISTING session");
byte rawTag[] = currentTag.getData();
//_log.debug("Pre IV for encryptExistingSession (aka tag): " + currentTag.toString());
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = _context.sha().calculateHash(rawTag);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, SessionTag.BYTE_LENGTH);
// that prepended SessionTag.BYTE_LENGTH bytes at the beginning of the buffer
System.arraycopy(rawTag, 0, aesEncr, 0, rawTag.length);
return aesEncr;
}
private final static Set EMPTY_SET = new HashSet();
/**
* For both scenarios, this method encrypts the AES area using the given key, iv
* and making sure the resulting data is at least as long as the paddedSize and
* also mod 16 bytes. The contents of the encrypted data is:
* - 2 byte integer specifying the # of session tags
* - that many 32 byte session tags
* - 4 byte integer specifying data.length
* - SHA256 of data
* - 1 byte flag that, if == 1, is followed by a new SessionKey
* - data
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
long paddedSize) {
return encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, 0);
}
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
long paddedSize, int prefixBytes) {
//_log.debug("iv for encryption: " + DataHelper.toString(iv, 16));
//_log.debug("Encrypting AES");
if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET;
int size = 2 // sizeof(tags)
+ tagsForDelivery.size()
+ SessionTag.BYTE_LENGTH*tagsForDelivery.size()
+ 4 // payload length
+ Hash.HASH_LENGTH
+ (newKey == null ? 1 : 1 + SessionKey.KEYSIZE_BYTES)
+ data.length;
int totalSize = size + getPaddingSize(size, paddedSize);
byte aesData[] = new byte[totalSize + prefixBytes];
int cur = prefixBytes;
DataHelper.toLong(aesData, cur, 2, tagsForDelivery.size());
cur += 2;
for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext();) {
SessionTag tag = (SessionTag) iter.next();
System.arraycopy(tag.getData(), 0, aesData, cur, SessionTag.BYTE_LENGTH);
cur += SessionTag.BYTE_LENGTH;
}
//_log.debug("# tags created, registered, and written: " + tagsForDelivery.size());
DataHelper.toLong(aesData, cur, 4, data.length);
cur += 4;
//_log.debug("data length: " + data.length);
Hash hash = _context.sha().calculateHash(data);
System.arraycopy(hash.getData(), 0, aesData, cur, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
if (newKey == null) {
aesData[cur++] = 0x00; // don't rekey
//_log.debug("flag written");
} else {
aesData[cur++] = 0x01; // rekey
System.arraycopy(newKey.getData(), 0, aesData, cur, SessionKey.KEYSIZE_BYTES);
cur += SessionKey.KEYSIZE_BYTES;
}
System.arraycopy(data, 0, aesData, cur, data.length);
cur += data.length;
//_log.debug("raw data written: " + len);
byte padding[] = getPadding(_context, size, paddedSize);
//_log.debug("padding length: " + padding.length);
System.arraycopy(padding, 0, aesData, cur, padding.length);
cur += padding.length;
//Hash h = _context.sha().calculateHash(data);
//_log.debug("Hash of entire aes block before encryption: (len=" + data.length + ")\n" + DataHelper.toString(h.getData(), 32));
_context.aes().encrypt(aesData, prefixBytes, aesData, prefixBytes, key, iv, aesData.length - prefixBytes);
//_log.debug("Encrypted length: " + aesEncr.length);
//return aesEncr;
return aesData;
}
/**
* Return random bytes for padding the data to a mod 16 size so that it is
* at least minPaddedSize
*
*/
final static byte[] getPadding(I2PAppContext context, int curSize, long minPaddedSize) {
int size = getPaddingSize(curSize, minPaddedSize);
byte rv[] = new byte[size];
context.random().nextBytes(rv);
return rv;
}
final static int getPaddingSize(int curSize, long minPaddedSize) {
int diff = 0;
if (curSize < minPaddedSize) {
diff = (int) minPaddedSize - curSize;
}
int numPadding = diff;
if (((curSize + diff) % 16) != 0) numPadding += (16 - ((curSize + diff) % 16));
return numPadding;
}
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
ElGamalAESEngine e = new ElGamalAESEngine(ctx);
Object kp[] = ctx.keyGenerator().generatePKIKeypair();
PublicKey pubKey = (PublicKey)kp[0];
PrivateKey privKey = (PrivateKey)kp[1];
SessionKey sessionKey = ctx.keyGenerator().generateSessionKey();
for (int i = 0; i < 10; i++) {
try {
Set tags = new HashSet(5);
if (i == 0) {
for (int j = 0; j < 5; j++)
tags.add(new SessionTag(true));
}
byte encrypted[] = e.encrypt("blah".getBytes(), pubKey, sessionKey, tags, 1024);
byte decrypted[] = e.decrypt(encrypted, privKey);
if ("blah".equals(new String(decrypted))) {
System.out.println("equal on " + i);
} else {
System.out.println("NOT equal on " + i + ": " + new String(decrypted));
break;
}
ctx.sessionKeyManager().tagsDelivered(pubKey, sessionKey, tags);
} catch (Exception ee) {
ee.printStackTrace();
break;
}
}
}
}

View File

@ -0,0 +1,276 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* 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 the TheCrypto 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.
*/
import java.math.BigInteger;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
/**
* Wrapper for ElGamal encryption/signature schemes.
*
* Does all of Elgamal now for data sizes of 223 bytes and less. The data to be
* encrypted is first prepended with a random nonzero byte, then the 32 bytes
* making up the SHA256 of the data, then the data itself. The random byte and
* the SHA256 hash is stripped on decrypt so the original data is returned.
*
* @author thecrypto, jrandom
*/
public class ElGamalEngine {
private Log _log;
private I2PAppContext _context;
/**
* The ElGamal engine should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public ElGamalEngine(I2PAppContext context) {
context.statManager().createRateStat("crypto.elGamal.encrypt",
"how long does it take to do a full ElGamal encryption", "Encryption",
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
context.statManager().createRateStat("crypto.elGamal.decrypt",
"how long does it take to do a full ElGamal decryption", "Encryption",
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
_context = context;
_log = context.logManager().getLog(ElGamalEngine.class);
}
private ElGamalEngine() { // nop
}
private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02});
private BigInteger[] getNextYK() {
return YKGenerator.getNextYK();
}
/** encrypt the data to the public key
* @return encrypted data
* @param publicKey public key encrypt to
* @param data data to encrypt
*/
public byte[] encrypt(byte data[], PublicKey publicKey) {
if ((data == null) || (data.length >= 223))
throw new IllegalArgumentException("Data to encrypt must be < 223 bytes at the moment");
if (publicKey == null) throw new IllegalArgumentException("Null public key specified");
long start = _context.clock().now();
byte d2[] = new byte[1+Hash.HASH_LENGTH+data.length];
d2[0] = (byte)0xFF;
Hash hash = _context.sha().calculateHash(data);
System.arraycopy(hash.getData(), 0, d2, 1, Hash.HASH_LENGTH);
System.arraycopy(data, 0, d2, 1+Hash.HASH_LENGTH, data.length);
long t0 = _context.clock().now();
BigInteger m = new NativeBigInteger(1, d2);
long t1 = _context.clock().now();
if (m.compareTo(CryptoConstants.elgp) >= 0)
throw new IllegalArgumentException("ARGH. Data cannot be larger than the ElGamal prime. FIXME");
long t2 = _context.clock().now();
BigInteger aalpha = new NativeBigInteger(1, publicKey.getData());
long t3 = _context.clock().now();
BigInteger yk[] = getNextYK();
BigInteger k = yk[1];
BigInteger y = yk[0];
long t7 = _context.clock().now();
BigInteger d = aalpha.modPow(k, CryptoConstants.elgp);
long t8 = _context.clock().now();
d = d.multiply(m);
long t9 = _context.clock().now();
d = d.mod(CryptoConstants.elgp);
long t10 = _context.clock().now();
byte[] ybytes = y.toByteArray();
byte[] dbytes = d.toByteArray();
byte[] out = new byte[514];
System.arraycopy(ybytes, 0, out, (ybytes.length < 257 ? 257 - ybytes.length : 0),
(ybytes.length > 257 ? 257 : ybytes.length));
System.arraycopy(dbytes, 0, out, (dbytes.length < 257 ? 514 - dbytes.length : 257),
(dbytes.length > 257 ? 257 : dbytes.length));
/*
StringBuffer buf = new StringBuffer(1024);
buf.append("Timing\n");
buf.append("0-1: ").append(t1 - t0).append('\n');
buf.append("1-2: ").append(t2 - t1).append('\n');
buf.append("2-3: ").append(t3 - t2).append('\n');
//buf.append("3-4: ").append(t4-t3).append('\n');
//buf.append("4-5: ").append(t5-t4).append('\n');
//buf.append("5-6: ").append(t6-t5).append('\n');
//buf.append("6-7: ").append(t7-t6).append('\n');
buf.append("7-8: ").append(t8 - t7).append('\n');
buf.append("8-9: ").append(t9 - t8).append('\n');
buf.append("9-10: ").append(t10 - t9).append('\n');
//_log.debug(buf.toString());
*/
long end = _context.clock().now();
long diff = end - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to encrypt ElGamal block (" + diff + "ms)");
}
_context.statManager().addRateData("crypto.elGamal.encrypt", diff, diff);
return out;
}
/** Decrypt the data
* @param encrypted encrypted data
* @param privateKey private key to decrypt with
* @return unencrypted data
*/
public byte[] decrypt(byte encrypted[], PrivateKey privateKey) {
if ((encrypted == null) || (encrypted.length > 514))
throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment");
long start = _context.clock().now();
byte[] ybytes = new byte[257];
byte[] dbytes = new byte[257];
System.arraycopy(encrypted, 0, ybytes, 0, 257);
System.arraycopy(encrypted, 257, dbytes, 0, 257);
BigInteger y = new NativeBigInteger(1, ybytes);
BigInteger d = new NativeBigInteger(1, dbytes);
BigInteger a = new NativeBigInteger(1, privateKey.getData());
BigInteger y1p = CryptoConstants.elgp.subtract(BigInteger.ONE).subtract(a);
BigInteger ya = y.modPow(y1p, CryptoConstants.elgp);
BigInteger m = ya.multiply(d);
m = m.mod(CryptoConstants.elgp);
byte val[] = m.toByteArray();
int i = 0;
for (i = 0; i < val.length; i++)
if (val[i] != (byte) 0x00) break;
//ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i);
byte hashData[] = new byte[Hash.HASH_LENGTH];
System.arraycopy(val, i + 1, hashData, 0, Hash.HASH_LENGTH);
Hash hash = new Hash(hashData);
int payloadLen = val.length - i - 1 - Hash.HASH_LENGTH;
if (payloadLen < 0) {
if (_log.shouldLog(Log.ERROR))
_log.error("Decrypted data is too small (" + (val.length - i)+ ")");
return null;
}
byte rv[] = new byte[payloadLen];
System.arraycopy(val, i + 1 + Hash.HASH_LENGTH, rv, 0, rv.length);
Hash calcHash = _context.sha().calculateHash(rv);
boolean ok = calcHash.equals(hash);
long end = _context.clock().now();
long diff = end - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Took too long to decrypt and verify ElGamal block (" + diff + "ms)");
}
_context.statManager().addRateData("crypto.elGamal.decrypt", diff, diff);
if (ok) {
//_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
return rv;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Doesn't match hash [sent hash=" + hash + "]\ndata = "
+ Base64.encode(rv), new Exception("Doesn't match"));
return null;
}
public static void main(String args[]) {
long eTime = 0;
long dTime = 0;
long gTime = 0;
int numRuns = 100;
if (args.length > 0) try {
numRuns = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) { // nop
}
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException ie) { // nop
}
RandomSource.getInstance().nextBoolean();
I2PAppContext context = new I2PAppContext();
System.out.println("Running " + numRuns + " times");
for (int i = 0; i < numRuns; i++) {
long startG = Clock.getInstance().now();
Object pair[] = KeyGenerator.getInstance().generatePKIKeypair();
long endG = Clock.getInstance().now();
PublicKey pubkey = (PublicKey) pair[0];
PrivateKey privkey = (PrivateKey) pair[1];
byte buf[] = new byte[128];
RandomSource.getInstance().nextBytes(buf);
long startE = Clock.getInstance().now();
byte encr[] = context.elGamalEngine().encrypt(buf, pubkey);
long endE = Clock.getInstance().now();
byte decr[] = context.elGamalEngine().decrypt(encr, privkey);
long endD = Clock.getInstance().now();
eTime += endE - startE;
dTime += endD - endE;
gTime += endG - startG;
if (!DataHelper.eq(decr, buf)) {
System.out.println("PublicKey : " + DataHelper.toString(pubkey.getData(), pubkey.getData().length));
System.out.println("PrivateKey : " + DataHelper.toString(privkey.getData(), privkey.getData().length));
System.out.println("orig : " + DataHelper.toString(buf, buf.length));
System.out.println("d(e(orig) : " + DataHelper.toString(decr, decr.length));
System.out.println("orig.len : " + buf.length);
System.out.println("d(e(orig).len : " + decr.length);
System.out.println("Not equal!");
System.exit(0);
} else {
System.out.println("*Run " + i + " is successful, with encr.length = " + encr.length + " [E: "
+ (endE - startE) + " D: " + (endD - endE) + " G: " + (endG - startG) + "]\n");
}
}
System.out.println("\n\nAll " + numRuns + " tests successful, average encryption time: " + (eTime / numRuns)
+ " average decryption time: " + (dTime / numRuns) + " average key generation time: "
+ (gTime / numRuns));
}
}

View File

@ -0,0 +1,30 @@
package net.i2p.crypto;
/**
* Allow various components with some entropy to feed that entropy back
* into some PRNG. The quality of the entropy provided varies, so anything
* harvesting should discriminate based on the offered "source" of the
* entropy, silently discarding insufficient entropy sources.
*
*/
public interface EntropyHarvester {
/**
* Feed the entropy pools with data[offset:offset+len]
*
* @param source origin of the entropy, allowing the harvester to
* determine how much to value the data
* @param offset index into the data array to start
* @param len how many bytes to use
*/
void feedEntropy(String source, byte data[], int offset, int len);
/**
* Feed the entropy pools with the bits in the data
*
* @param source origin of the entropy, allowing the harvester to
* determine how much to value the data
* @param bitoffset bit index into the data array to start
* (using java standard big-endian)
* @param bits how many bits to use
*/
void feedEntropy(String source, long data, int bitoffset, int bits);
}

View File

@ -0,0 +1,51 @@
package net.i2p.crypto;
import gnu.crypto.hash.Sha256Standalone;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.macs.HMac;
/**
* Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs
* in {@link org.bouncycastle.crypto.macs.HMac} and
* {@link org.bouncycastle.crypto.digests.MD5Digest}.
*
*/
public class HMAC256Generator extends HMACGenerator {
public HMAC256Generator(I2PAppContext context) { super(context); }
protected HMac acquire() {
synchronized (_available) {
if (_available.size() > 0)
return (HMac)_available.remove(0);
}
// the HMAC is hardcoded to use SHA256 digest size
// for backwards compatability. next time we have a backwards
// incompatible change, we should update this by removing ", 32"
return new HMac(new Sha256ForMAC());
}
private class Sha256ForMAC extends Sha256Standalone implements Digest {
public String getAlgorithmName() { return "sha256 for hmac"; }
public int getDigestSize() { return 32; }
public int doFinal(byte[] out, int outOff) {
byte rv[] = digest();
System.arraycopy(rv, 0, out, outOff, rv.length);
reset();
return rv.length;
}
}
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte data[] = new byte[64];
ctx.random().nextBytes(data);
SessionKey key = ctx.keyGenerator().generateSessionKey();
Hash mac = ctx.hmac256().calculate(key, data);
System.out.println(Base64.encode(mac.getData()));
}
}

Some files were not shown because too many files have changed in this diff Show More