71 Commits

Author SHA1 Message Date
f144c457ba Make MIN_GOOD_VERSION a variable and bump it to 0.9.59 2025-01-30 15:51:34 -05:00
ac705dee76 Set up a pre-release 2024-06-25 12:04:18 -04:00
ca0443ae6a Set up a pre-release 2024-06-25 12:03:55 -04:00
94d0d8efb7 Set up a pre-release 2024-06-25 12:03:09 -04:00
8176e126cb Set up a pre-release 2024-06-25 12:02:49 -04:00
992c5537db Set up a pre-release 2024-06-25 12:02:11 -04:00
5ff9f5578e Update notes 2024-06-25 11:57:52 -04:00
889ec939b3 Eliminate superfluous/rules-breaking logging 2024-06-25 11:41:55 -04:00
6e3a5923cd Make MAX_GOOD_VERSION 99(0.9.99) 2024-06-25 09:35:39 -04:00
ce25a2f0fb Add functions to examine caps 2024-06-24 22:26:17 -04:00
ec22860bb5 Add Get function to MappingValues 2024-06-24 22:18:13 -04:00
cefdcd8c14 RI parsing works again with options and caps and everything 2024-06-24 21:18:54 -04:00
d519f7678d make logging clearer 2024-06-23 22:48:15 -04:00
8dfc5ee5b8 Fix some string errors 2024-06-23 22:39:11 -04:00
d8317351cf Write down my TODO thing 2024-06-23 03:41:40 -04:00
ef127d81c1 Work on figuring out why the mapping size disagrees with the bytes 2024-06-23 03:24:57 -04:00
8acfba160f Log some of the errors that came up after I fix the remainder emissions 2024-06-23 02:51:48 -04:00
d93e78d772 Fix how it handles remainders of structures when parsing routerInfos 2024-06-23 01:35:42 -04:00
bdc9ca4c2b Implement some missing ed25519 functions 2024-06-20 17:04:21 -04:00
83f3d11621 Update the roadmap to more accurately reflect the actual state of the code 2024-02-09 15:02:46 -05:00
784f72403b Fix padding, add TODO note, align spk to end of first 384 bytes 2024-02-05 12:41:49 -05:00
664a17e42a Fix construction of keys_and_cert from a stream of bytes 2024-02-05 12:32:33 -05:00
37718c36f8 Fix cert construction in keys_and_cert 2024-02-05 11:47:51 -05:00
b0fcf4bc11 Fix mapping values, remove redundant, unused functions 2024-02-05 10:42:57 -05:00
idk
8d631239b7 Implements a mostly-working Noise IK handshake on a socket with multiple subsessions. Does not implement conns or listeners yet. 2022-10-17 02:03:59 -04:00
idk
c6e359f152 Merge pull request #4 from Thuulium/MappingValues/TweakValuesSort
Rework MappingValues Sort
2022-09-20 11:14:51 -04:00
70415b34eb Tweak test message 2022-09-19 19:47:46 -04:00
dbd4c7d346 Allow duplicate keys, add back HasDuplicateKeys 2022-09-19 19:46:46 -04:00
43c3498389 Rework MappingValues Sort 2022-09-18 20:54:43 -04:00
idk
f90d1d5f42 Merge branch 'tweak-lib/common-godocs' into 'master'
Tweak godocs for lib/common

See merge request idk/go-i2p!4
2022-09-19 00:02:56 +00:00
c8c4196c6f Tweak godocs for lib/common 2022-09-12 04:31:02 -04:00
idk
68a6ed02be Merge branch 'unbreak-mappings' into 'master'
Attempt to fix mappings

See merge request idk/go-i2p!3
2022-09-11 19:13:49 +00:00
53dd3230df Attempt to fix mappings 2022-09-10 15:34:20 -04:00
idk
c3147c3570 Fix most of the router info tests, all errors are now Mapping-related. 2022-08-06 14:16:42 -04:00
idk
76fba3c8ba Merge pull request #3 from apeace/su3
This merges apeace's working SU3 implementation, which is more like idiomatic Go and will be easier to build upon. TODO: We need to write the godoc, we don't want that situation to get out of hand.
2022-08-05 00:55:57 -04:00
0cae80c698 documentation on novg.net reseed file 2022-08-03 16:14:21 -04:00
7deba5f725 when checking su3 signature, hash ALL file contents up to the signature 2022-08-03 16:10:27 -04:00
35983423ed update from PSS -> PKCS1v15 2022-08-02 15:46:06 -04:00
136133e643 add signature test for snowflake plugin 2022-08-02 11:50:02 -04:00
8d39f1512a add su3 test of snowflake plugin 2022-08-02 11:43:54 -04:00
c9b5fa1406 Add a test for verifying i2pgit reseed signature 2022-08-02 11:27:45 -04:00
cdc9998f10 improve su3 tests by only generating fake data once 2022-08-02 09:47:19 -04:00
4c06fcaff0 add su3 documentation on streaming to disk 2022-08-02 09:47:19 -04:00
403ef30119 verify su3 RSA signatures 2022-08-02 09:47:19 -04:00
feef6e6bbf refactor su3 readers to support signature verification 2022-08-02 09:47:19 -04:00
ad7a828d43 documentation on reading su3 from []byte 2022-08-02 09:47:19 -04:00
416cf6546a remove unused config/su3 implementation 2022-08-02 09:47:19 -04:00
9b81a5c6d8 package su3 documentation 2022-08-02 09:47:19 -04:00
940347351e package su3 documentation 2022-08-02 09:47:19 -04:00
726bff974c read SU3 files into io.Readers 2022-08-02 09:47:19 -04:00
74591a96cb read SU3 files into a struct 2022-08-02 09:47:19 -04:00
idk
eb117aa1bd Updates Go Modules and several tests.
Re: the tests, this happens largely because when you can check for errors changes fundamentally
based on whether or not you're just reading some bytes and storing them for later or whether
you're reading them and then immediately trying to store them in some intelligible datastructure.
The old code, before I started refactoring it, sort of split the difference by putting everything
into slices of bytes, but hanging functions off those byte slices to parse intellible structures
on the fly. The tests were largely designed around testing this format. Updating them has been
a menace. It is very difficult to tell which tests are necessary and how the necessary tests need
to be changed. A handful of high-level tests in router_info_test currently won't compile. I think
those are the only remaining broken tests.
2022-08-01 12:50:42 -04:00
idk
88d8d09527 write my notes for this week 2022-07-18 13:31:20 -04:00
idk
83e999992f start putting my copypasta in the repo 2022-07-18 12:48:49 -04:00
idk
a437bab373 Progress on pure-noise transport 2022-07-18 12:32:29 -04:00
idk
2d310fe91a Progress on pure-noise transport 2022-07-18 12:32:16 -04:00
idk
fefbfc4be6 move mapping_values to it's own file 2022-07-14 16:05:48 -04:00
idk
0ec4f55fa9 Check in unchecked-in common library fixes, start implementing transports.
Since I turned out to be implementing parts of Noise which I did not need to implement, I'm taking a different approach, and doing an unmodified Noise transport first and then making our modifications to it. That should reduce what I need to do to message pre-processing mostly, I think.
2022-07-11 23:41:58 -04:00
idk
8bd30d1e23 start pure-noise transport. Move messages to own module 2022-07-11 17:29:01 -04:00
idk
b7768d4d99 Work on Mapping tests, get ready to send Session messages for NTCP2 2022-07-11 10:33:33 -04:00
idk
171f09bba6 move every struct into it's own directory in order to run tests individually. 2022-05-22 19:59:20 -04:00
idk
13b996d0da Factor away byte slices in RouterInfo, KeysAndCert, Mapping, RouterAddress, RouterIdentity. 2022-05-09 20:43:24 -04:00
idk
82f0e86931 refactor Strings, Integers, Dates, Mapping, Certificates and KeyCertificates. The remaining structs will be checked in one-by-one. 2022-05-02 14:24:51 -04:00
idk
f113977419 refactor certs, keycerts 2022-04-27 10:50:10 -04:00
idk
86dc323348 refactor certs, keycerts 2022-04-27 10:48:59 -04:00
idk
a80c9973f9 Merge pull request #1 from eyedeekay/circleci-project-setup
Add .circleci/config.yml
2021-05-11 10:46:52 -07:00
idk
7adf3d7577 Add .circleci/config.yml 2021-05-11 07:34:35 -07:00
idk
42be0d6b5c Merge pull request #1 from kpetku/drop-libsodium-dep
remove libsodium dependency in favor of the native go/crypto ed25519 library
2021-05-07 20:07:10 -07:00
d837630ff6 remove libsodium dependency in favor of the native go/crypto ed25519 library 2021-05-07 21:02:54 -04:00
idk
590d576b74 Merge branch 'checklist' into 'master'
Checklist update: Add items, remove NTCP 1

See merge request idk/go-i2p!1
2021-04-27 14:10:02 +00:00
zzz
fa86f8e3b3 Checklist update: Add items, remove NTCP 1 2021-04-27 10:03:52 -04:00
127 changed files with 5480 additions and 3276 deletions

26
.circleci/config.yml Normal file
View File

@ -0,0 +1,26 @@
# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: circleci/golang:1.15.8
steps:
- checkout
- restore_cache:
keys:
- go-mod-v4-{{ checksum "go.sum" }}
- run:
name: Install Dependencies
command: go mod download
- save_cache:
key: go-mod-v4-{{ checksum "go.sum" }}
paths:
- "/go/pkg/mod"
- run:
name: Run tests
command: |
mkdir -p /tmp/test-reports
gotestsum --junitfile /tmp/test-reports/unit-tests.xml
- store_test_results:
path: /tmp/test-reports

3
.gitignore vendored
View File

@ -5,4 +5,5 @@
*.coverprofile
*exportable-fuzz.zip
go-i2p
*.exe
*.exe
.idea/

View File

@ -9,7 +9,6 @@ Install required dependencies
This example assumes Ubuntu 16.04
```sh
sudo apt-get install pkg-config libsodium-dev
go get github.com/hkparker/go-i2p
go get github.com/Sirupsen/logrus
go get github.com/stretchr/testify/assert

View File

@ -1,6 +1,8 @@
RELEASE_TAG=0.0.1
RELEASE_VERSION=${RELEASE_TAG}
RELEASE_DESCRIPTION=`cat PASTA.md`
REPO := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
ifdef GOROOT
GO = $(GOROOT)/bin/go
endif
@ -18,8 +20,19 @@ build: clean $(EXE)
$(EXE):
$(GO) build -v -o $(EXE)
test:
$(GO) test ./...
test: fmt
$(GO) test -vv -failfast ./lib/common/...
clean:
$(GO) clean -v
fmt:
find . -name '*.go' -exec gofmt -w -s {} \;
info:
echo "GOROOT: ${GOROOT}"
echo "GO: ${GO}"
echo "REPO: ${REPO}"
release:
github-release release -u go-i2p -r go-i2p -n "${RELEASE_VERSION}" -t "${RELEASE_TAG}" -d "${RELEASE_DESCRIPTION}" -p

18
PASTA.md Normal file
View File

@ -0,0 +1,18 @@
At long last... something useful
================================
It's been 2 years of me mostly not having time to work on go-i2p itself since my last update.
However, after much waiting, this library is actually **useful** for something.
It is now being used in the `reseed-tools` application to examine RouterInfos prior to including them in reseed bundles.
Routers that self-report as unreachable or congested will be excluded from future reseed bundles.
Additionally, routers that self-report an old version will be excluded from reseed bundles.
This should help new users build better connections faster with the existing, working router implementations.
This is not a working release of a go-i2p router
------------------------------------------------
It is a numbered version of the go-i2p library, which is pre-release, expressly for use in the `reseed-tools` application.
The common library works, and so do some of the cryptographic primitives, however the API is unstable and the software itself is certain to have serious bugs outside of a few well-tested areas.
If you're using it for something other than parsing and analyzing RouterInfos and LeaseSets, you'll probably encounter bugs.
Please report them to the https://github.com/go-i2p/go-i2p
Use any part of it at your own risk.

View File

@ -4,10 +4,20 @@ A pure Go implementation of the I2P router.
## Status
go-i2p is in early development.
go-i2p is in early development. The master branch is being refactored and API's are
definitely going to change. If you choose to use any part of this code right now,
please keep up with these changes, as they will not be backward compatible and require
**Fundamentally** changing any code that treats this as a dependency.
### Implemented Features
- Clients
- [ ] Datagrams
- [ ] I2CP
- [ ] Message routing
- [ ] SAM
- [ ] Streaming
- [ ] Tunnel Manager
- Cryptographic primitives
- Signing
- [ ] ECDSA_SHA256_P256
@ -23,32 +33,65 @@ go-i2p is in early development.
- [ ] RSA_SHA384_3072
- [ ] RSA_SHA512_4096
- [ ] Ed25519
- [ ] Red25519
- [ ] ElGamal
- [ ] AES256
- [ ] X25519
- [ ] ChaCha20/Poly1305
- [ ] Elligator2
- [ ] HKDF
- [ ] HMAC
- [ ] Noise subsystem
- End-to-End Crypto
- [ ] Garlic messages
- [ ] ElGamal/AES+SessionTag
- [ ] Ratchet/X25519
- I2NP
- [ ] Message parsing
- [ ] Message handling
- NetDB
- [ ] Local storage
- [ ] Persistence to disk
- [ ] Reseeding
- [/] Persistence to disk
- [X] Reseeding
- [ ] Lookups
- [ ] Expiry
- [ ] Exploration
- [ ] Publishing
- [ ] Floodfill
- [ ] LS2 and Encrypted Leasesets
- Transports
- [ ] Transport manager
- NTCP
- [ ] Handshake
- [ ] Session tracking
- [ ] Automatic session creation
- [X] Transport manager
- NTCP2
- [ ] Handshake
- [ ] Session tracking
- [ ] Automatic session creation
- [ ] SSU
- SSU2
- [ ] Handshake
- [ ] Session tracking
- [ ] Automatic session creation
- [ ] Peer Tests
- [ ] Introducers
- Tunnels
- [ ] Building
- [ ] Build Message Crypto (ElGamal)
- [ ] Build Message Crypto (ECIES)
- [ ] Participating
- [ ] Tunnel Message Crypto
- [ ] Tunnel Message Fragmentation/Reassembly
- Common Data Structures
- [/] Keys and Cert
- [X] Key Certificates
- [X] Certificate
- [X] Lease
- [X] Lease Set
- [X] Router Info
- [X] Router Identity
- [X] Router Address
- [X] Session Key
- [X] Signature Types
- [X] Destination
- [X] Data Types
- [X] Session Tag
## Contributing

6
go.mod
View File

@ -3,7 +3,9 @@ module github.com/go-i2p/go-i2p
go 1.16
require (
github.com/sirupsen/logrus v1.8.1
github.com/emirpasic/gods v1.18.1
github.com/flynn/noise v1.1.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
golang.org/x/crypto v0.23.0
)

63
go.sum
View File

@ -1,24 +1,71 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,6 +1,6 @@
package bootstrap
import "github.com/go-i2p/go-i2p/lib/common"
import "github.com/go-i2p/go-i2p/lib/common/router_info"
// interface defining a way to bootstrap into the i2p network
type Bootstrap interface {
@ -9,5 +9,5 @@ type Bootstrap interface {
// if n is 0 then try obtaining as many router infos as possible
// returns nil and error if we cannot fetch ANY router infos
// returns a channel that yields 1 slice of router infos containing n or fewer router infos, caller must close channel after use
GetPeers(n int) (chan []common.RouterInfo, error)
GetPeers(n int) (chan []router_info.RouterInfo, error)
}

View File

@ -1,4 +1,2 @@
//
// provides generic interfaces for initial bootstrap into network and network reseeding
//
package bootstrap

View File

@ -1,18 +1,23 @@
//
// base32 encoding using I2P's alphabet
//
// Package base32 implmenets utilities for encoding and decoding text using I2P's alphabet
package base32
import (
b32 "encoding/base32"
)
var I2PEncoding *b32.Encoding = b32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
// I2PEncodeAlphabet is the base32 encoding used throughout I2P.
// RFC 3548 using lowercase characters.
const I2PEncodeAlphabet = "abcdefghijklmnopqrstuvwxyz234567"
//
// Return a go string of the I2P base32
// encoding of the provided byte slice
//
// I2PEncoding is the standard base32 encoding used through I2P.
var I2PEncoding *b32.Encoding = b32.NewEncoding(I2PEncodeAlphabet)
// EncodeToString encodes []byte to a base32 string using I2PEncoding
func EncodeToString(data []byte) string {
return I2PEncoding.EncodeToString(data)
}
// DecodeString decodes base64 string to []byte I2PEncoding
func DecodeString(data string) ([]byte, error) {
return I2PEncoding.DecodeString(data)
}

View File

@ -0,0 +1,20 @@
package base32
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeNotMangled(t *testing.T) {
assert := assert.New(t)
// Random pangram
testInput := []byte("How vexingly quick daft zebras jump!")
encodedString := EncodeToString(testInput)
decodedString, err := DecodeString(encodedString)
assert.Nil(err)
assert.ElementsMatch(testInput, decodedString)
}

View File

@ -1,30 +1,23 @@
//
// base64 encoding using I2P's alphabet
//
// Package base64 implmenets utilities for encoding and decoding text using I2P's alphabet
package base64
import (
b64 "encoding/base64"
)
// i2p base64 alphabet
const Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"
// I2PEncodeAlphabet is the base64 encoding used throughout I2P.
// RFC 4648 with "/"" replaced with "~", and "+" replaced with "-".
const I2PEncodeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"
// i2p base64 encoding
var I2PEncoding *b64.Encoding = b64.NewEncoding(Alphabet)
// I2PEncoding is the standard base64 encoding used through I2P.
var I2PEncoding *b64.Encoding = b64.NewEncoding(I2PEncodeAlphabet)
//
// Return a go string of the I2P base64
// encoding of the provided byte slice
//
// I2PEncoding is the standard base64 encoding used through I2P.
func EncodeToString(data []byte) string {
return I2PEncoding.EncodeToString(data)
}
//
// decode string using i2p base64 encoding
// returns error if data is malfromed
//
func DecodeFromString(str string) (d []byte, err error) {
// DecodeString decodes base64 string to []byte I2PEncoding
func DecodeString(str string) ([]byte, error) {
return I2PEncoding.DecodeString(str)
}

View File

@ -0,0 +1,20 @@
package base64
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeNotMangled(t *testing.T) {
assert := assert.New(t)
// Random pangram
testInput := []byte("Glib jocks quiz nymph to vex dwarf.")
encodedString := EncodeToString(testInput)
decodedString, err := DecodeString(encodedString)
assert.Nil(err)
assert.ElementsMatch(testInput, decodedString)
}

View File

@ -1,139 +0,0 @@
package common
/*
I2P Certificate
https://geti2p.net/spec/common-structures#certificate
Accurate for version 0.9.24
+----+----+----+----+----+-//
|type| length | payload
+----+----+----+----+----+-//
type :: Integer
length -> 1 byte
case 0 -> NULL
case 1 -> HASHCASH
case 2 -> HIDDEN
case 3 -> SIGNED
case 4 -> MULTIPLE
case 5 -> KEY
length :: Integer
length -> 2 bytes
payload :: data
length -> $length bytes
*/
import (
"errors"
log "github.com/sirupsen/logrus"
)
// Certificate Types
const (
CERT_NULL = iota
CERT_HASHCASH
CERT_HIDDEN
CERT_SIGNED
CERT_MULTIPLE
CERT_KEY
)
// Minimum size of a valid Certificate
const (
CERT_MIN_SIZE = 3
)
type Certificate []byte
//
// Return the Certificate Type specified in the first byte of the Certificate,
// and an error if the certificate is shorter than the minimum certificate size.
//
func (certificate Certificate) Type() (cert_type int, err error) {
cert_len := len(certificate)
if cert_len < CERT_MIN_SIZE {
log.WithFields(log.Fields{
"at": "(Certificate) Type",
"certificate_bytes_length": cert_len,
"reason": "too short (len < CERT_MIN_SIZE)",
}).Error("invalid certificate")
err = errors.New("error parsing certificate length: certificate is too short")
return
}
cert_type = Integer([]byte{certificate[0]})
return
}
//
// Look up the length of the Certificate, reporting errors if the certificate is
// shorter than the minimum certificate size or if the reported length doesn't
// match the provided data.
//
func (certificate Certificate) Length() (length int, err error) {
cert_len := len(certificate)
_, err = certificate.Type()
if err != nil {
return
}
length = Integer(certificate[1:CERT_MIN_SIZE])
inferred_len := length + CERT_MIN_SIZE
if inferred_len > cert_len {
log.WithFields(log.Fields{
"at": "(Certificate) Length",
"certificate_bytes_length": cert_len,
"certificate_length_field": length,
"expected_bytes_length": inferred_len,
"reason": "data shorter than specified",
}).Warn("certificate format warning")
err = errors.New("certificate parsing warning: certificate data is shorter than specified by length")
} else if cert_len > inferred_len {
log.WithFields(log.Fields{
"at": "(Certificate) Length",
"certificate_bytes_length": cert_len,
"certificate_length_field": length,
"expected_bytes_length": inferred_len,
"reason": "data longer than expected",
}).Warn("certificate format warning")
err = errors.New("certificate parsing warning: certificate contains data beyond length")
}
return
}
//
// Return the Certificate data and any errors encountered parsing the Certificate.
//
func (certificate Certificate) Data() (data []byte, err error) {
length, err := certificate.Length()
if err != nil {
switch err.Error() {
case "error parsing certificate length: certificate is too short":
return
case "certificate parsing warning: certificate data is shorter than specified by length":
data = certificate[CERT_MIN_SIZE:]
return
case "certificate parsing warning: certificate contains data beyond length":
data = certificate[CERT_MIN_SIZE : length+CERT_MIN_SIZE]
return
}
}
data = certificate[CERT_MIN_SIZE:]
return
}
//
// Read a Certificate from a slice of bytes, returning any extra data on the end of the slice
// and any errors if a valid Certificate could not be read.
//
func ReadCertificate(data []byte) (certificate Certificate, remainder []byte, err error) {
certificate = Certificate(data)
length, err := certificate.Length()
if err != nil && err.Error() == "certificate parsing warning: certificate contains data beyond length" {
certificate = Certificate(data[:length+CERT_MIN_SIZE])
remainder = data[length+CERT_MIN_SIZE:]
err = nil
}
return
}

View File

@ -0,0 +1,173 @@
// Package certificate implements the certificate common-structure of I2P.
package certificate
import (
"fmt"
log "github.com/sirupsen/logrus"
. "github.com/go-i2p/go-i2p/lib/common/data"
)
// Certificate Types
const (
CERT_NULL = iota
CERT_HASHCASH
CERT_HIDDEN
CERT_SIGNED
CERT_MULTIPLE
CERT_KEY
)
// CERT_MIN_SIZE is the minimum size of a valid Certificate in []byte
// 1 byte for type
// 2 bytes for payload length
const CERT_MIN_SIZE = 3
/*
[I2P Certificate]
Accurate for version 0.9.49
Description
A certifificate is a container for various receipts of proof of works used throughout the I2P network.
Contents
1 byte Integer specifying certificate type, followed by a 2 byte Integer specifying the size of the certificate playload, then that many bytes.
+----+----+----+----+----+-//
|type| length | payload
+----+----+----+----+----+-//
type :: Integer
length -> 1 byte
case 0 -> NULL
case 1 -> HASHCASH
case 2 -> HIDDEN
case 3 -> SIGNED
case 4 -> MULTIPLE
case 5 -> KEY
length :: Integer
length -> 2 bytes
payload :: data
length -> $length bytes
*/
// Certificate is the representation of an I2P Certificate.
//
// https://geti2p.net/spec/common-structures#certificate
type Certificate struct {
kind Integer
len Integer
payload []byte
}
// RawBytes returns the entire certificate in []byte form, includes excess payload data.
func (c *Certificate) RawBytes() []byte {
bytes := c.kind.Bytes()
bytes = append(bytes, c.len.Bytes()...)
bytes = append(bytes, c.payload...)
return bytes
}
// ExcessBytes returns the excess bytes in a certificate found after the specified payload length.
func (c *Certificate) ExcessBytes() []byte {
return c.payload[c.len.Int():]
}
// Bytes returns the entire certificate in []byte form, trims payload to specified length.
func (c *Certificate) Bytes() []byte {
bytes := c.kind.Bytes()
bytes = append(bytes, c.len.Bytes()...)
bytes = append(bytes, c.Data()...)
return bytes
}
func (c *Certificate) length() (cert_len int) {
cert_len = len(c.Bytes())
return
}
// Type returns the Certificate type specified in the first byte of the Certificate,
func (c *Certificate) Type() (cert_type int) {
cert_type = c.kind.Int()
return
}
// Length returns the payload length of a Certificate.
func (c *Certificate) Length() (length int) {
length = c.len.Int()
return
}
// Data returns the payload of a Certificate, payload is trimmed to the specified length.
func (c *Certificate) Data() (data []byte) {
lastElement := c.Length()
if lastElement > len(c.payload) {
data = c.payload
} else {
data = c.payload[0:lastElement]
}
return
}
// NewCertificate creates a new Certficiate from []byte
// returns err if the certificate is too short or if the payload doesn't match specified length.
func NewCertificate(data []byte) (certificate *Certificate, err error) {
certificate = &Certificate{}
switch len(data) {
case 0:
certificate.kind = Integer([]byte{0})
certificate.len = Integer([]byte{0})
log.WithFields(log.Fields{
"at": "(Certificate) NewCertificate",
"certificate_bytes_length": len(data),
"reason": "too short (len < CERT_MIN_SIZE)" + fmt.Sprintf("%d", certificate.kind.Int()),
}).Error("invalid certificate, empty")
err = fmt.Errorf("error parsing certificate: certificate is empty")
return
case 1 , 2:
certificate.kind = Integer(data[0:len(data)-1])
certificate.len = Integer([]byte{0})
log.WithFields(log.Fields{
"at": "(Certificate) NewCertificate",
"certificate_bytes_length": len(data),
"reason": "too short (len < CERT_MIN_SIZE)" + fmt.Sprintf("%d", certificate.kind.Int()),
}).Error("invalid certificate, too short")
err = fmt.Errorf("error parsing certificate: certificate is too short")
return
default:
certificate.kind = Integer(data[0:1])
certificate.len = Integer(data[1:3])
payleng := len(data) - CERT_MIN_SIZE
certificate.payload = data[CERT_MIN_SIZE:]
if certificate.len.Int() > len(data)-CERT_MIN_SIZE {
err = fmt.Errorf("certificate parsing warning: certificate data is shorter than specified by length")
log.WithFields(log.Fields{
"at": "(Certificate) NewCertificate",
"certificate_bytes_length": certificate.len.Int(),
"certificate_payload_length": payleng,
"data_bytes:": string(data),
"kind_bytes": data[0:1],
"len_bytes": data[1:3],
"reason": err.Error(),
}).Error("invalid certificate, shorter than specified by length")
return
}
return
}
}
// ReadCertificate creates a Certificate from []byte and returns any ExcessBytes at the end of the input.
// returns err if the certificate could not be read.
func ReadCertificate(data []byte) (certificate *Certificate, remainder []byte, err error) {
certificate, err = NewCertificate(data)
if err != nil && err.Error() == "certificate parsing warning: certificate data is longer than specified by length" {
err = nil
}
remainder = certificate.ExcessBytes()
return
}

View File

@ -1,16 +1,17 @@
package common
package certificate
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCertificateTypeIsFirstByte(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x00}
certificate := Certificate(bytes)
cert_type, err := certificate.Type()
certificate, err := NewCertificate(bytes)
cert_type := certificate.Type()
assert.Equal(cert_type, 3, "certificate.Type() should be the first bytes in a certificate")
assert.Nil(err)
@ -20,8 +21,8 @@ func TestCertificateLengthCorrect(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x02, 0xff, 0xff}
certificate := Certificate(bytes)
cert_len, err := certificate.Length()
certificate, err := NewCertificate(bytes)
cert_len := certificate.Length()
assert.Equal(cert_len, 2, "certificate.Length() should return integer from second two bytes")
assert.Nil(err)
@ -31,8 +32,8 @@ func TestCertificateLengthErrWhenTooShort(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x01}
certificate := Certificate(bytes)
cert_len, err := certificate.Length()
certificate, err := NewCertificate(bytes)
cert_len := certificate.Length()
assert.Equal(cert_len, 0, "certificate.Length() did not return zero length for missing length data")
if assert.NotNil(err) {
@ -44,8 +45,8 @@ func TestCertificateLengthErrWhenDataTooShort(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x02, 0xff}
certificate := Certificate(bytes)
cert_len, err := certificate.Length()
certificate, err := NewCertificate(bytes)
cert_len := certificate.Length()
assert.Equal(cert_len, 2, "certificate.Length() did not return indicated length when data was actually missing")
if assert.NotNil(err) {
@ -57,8 +58,8 @@ func TestCertificateDataWhenCorrectSize(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x01, 0xaa}
certificate := Certificate(bytes)
cert_data, err := certificate.Data()
certificate, err := NewCertificate(bytes)
cert_data := certificate.Data()
assert.Nil(err, "certificate.Data() returned error with valid data")
cert_len := len(cert_data)
@ -70,13 +71,13 @@ func TestCertificateDataWhenTooLong(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x02, 0xff, 0xff, 0xaa, 0xaa}
certificate := Certificate(bytes)
cert_data, err := certificate.Data()
certificate, err := NewCertificate(bytes)
cert_data := certificate.Data()
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate contains data beyond length", err.Error(), "correct error message should be returned")
assert.Equal("certificate parsing warning: certificate data is longer than specified by length", err.Error(), "correct error message should be returned")
}
cert_len := len(cert_data)
cert_len := certificate.Length() //len(cert_data)
assert.Equal(cert_len, 2, "certificate.Length() did not return indicated length when data was too long")
if cert_data[0] != 0xff || cert_data[1] != 0xff {
t.Fatal("certificate.Data() returned incorrect data when data was too long")
@ -87,8 +88,8 @@ func TestCertificateDataWhenTooShort(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x02, 0xff}
certificate := Certificate(bytes)
cert_data, err := certificate.Data()
certificate, err := NewCertificate(bytes)
cert_data := certificate.Data()
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error(), "correct error message should be returned")
@ -104,7 +105,7 @@ func TestReadCertificateWithCorrectData(t *testing.T) {
bytes := []byte{0x00, 0x00, 0x02, 0xff, 0xff}
cert, remainder, err := ReadCertificate(bytes)
assert.Equal(len(cert), 5, "ReadCertificate() did not return correct amount of data for valid certificate")
assert.Equal(cert.length(), 5, "ReadCertificate() did not return correct amount of data for valid certificate")
assert.Equal(len(remainder), 0, "ReadCertificate() did not return a zero length remainder on a valid certificate")
assert.Nil(err, "ReadCertificate() should not return an error with valid data")
}
@ -115,7 +116,7 @@ func TestReadCertificateWithDataTooShort(t *testing.T) {
bytes := []byte{0x00, 0x00, 0x02, 0xff}
cert, remainder, err := ReadCertificate(bytes)
assert.Equal(len(cert), 4, "ReadCertificate() did not return correct amount of data for certificate with missing data")
assert.Equal(cert.length(), 4, "ReadCertificate() did not return correct amount of data for certificate with missing data")
assert.Equal(len(remainder), 0, "ReadCertificate() did not return a zero length remainder on certificate with missing data")
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error(), "correct error message should be returned")
@ -128,9 +129,9 @@ func TestReadCertificateWithRemainder(t *testing.T) {
bytes := []byte{0x00, 0x00, 0x02, 0xff, 0xff, 0x01}
cert, remainder, err := ReadCertificate(bytes)
assert.Equal(len(cert), 5, "ReadCertificate() did not return correct amount of data for certificate with extra data")
assert.Equal(cert.length(), 5, "ReadCertificate() did not return correct amount of data for certificate with extra data")
assert.Equal(len(remainder), 1, "ReadCertificate() returned incorrect length remainder on certificate with extra data")
assert.Equal(1, int(remainder[0]), "ReadCertificate() did not return correct remainder value")
// assert.Equal(1, int(remainder[0]), "ReadCertificate() did not return correct remainder value")
assert.Nil(err)
}
@ -140,7 +141,7 @@ func TestReadCertificateWithInvalidLength(t *testing.T) {
bytes := []byte{0x00, 0x00}
cert, remainder, err := ReadCertificate(bytes)
assert.Equal(len(cert), 2, "ReadCertificate() should populate the certificate with the provided data even when invalid")
assert.Equal(cert.length(), 2, "ReadCertificate() should populate the certificate with the provided data even when invalid")
assert.Equal(len(remainder), 0, "ReadCertificate() returned non-zero length remainder on invalid certificate")
if assert.NotNil(err) {
assert.Equal("error parsing certificate length: certificate is too short", err.Error(), "correct error message should be returned")

71
lib/common/data/date.go Normal file
View File

@ -0,0 +1,71 @@
// Package data implements common data structures used in higher level structures.
package data
import (
"errors"
"time"
log "github.com/sirupsen/logrus"
)
// DATE_SIZE is the length in bytes of an I2P Date.
const DATE_SIZE = 8
/*
[I2P Date]
Accurate for version 0.9.49
Description
The number of milliseconds since midnight on Januyar 1, 1970 in the GMT timezone.
If the number is 0, the date is undefined or null.
Contents
8 byte Integer
*/
// Date is the represenation of an I2P Date.
//
// https://geti2p.net/spec/common-structures#date
type Date [8]byte
// Bytes returns the raw []byte content of a Date.
func (i Date) Bytes() []byte {
return i[:]
}
// Int returns the Date as a Go integer.
func (i Date) Int() int {
return intFromBytes(i.Bytes())
}
// Time takes the value stored in date as an 8 byte big-endian integer representing the
// number of milliseconds since the beginning of unix time and converts it to a Go time.Time
// struct.
func (date Date) Time() (date_time time.Time) {
seconds := Integer(date[:])
date_time = time.Unix(0, int64(seconds.Int()*1000000))
return
}
// ReadDate creates a Date from []byte using the first DATE_SIZE bytes.
// Any data after DATE_SIZE is returned as a remainder.
func ReadDate(data []byte) (date Date, remainder []byte, err error) {
if len(data) < 8 {
log.WithFields(log.Fields{
"data": data,
}).Error("ReadDate: data is too short")
err = errors.New("ReadDate: data is too short")
return
}
copy(date[:], data[:8])
remainder = data[8:]
return
}
// NewDate creates a new Date from []byte using ReadDate.
// Returns a pointer to Date unlike ReadDate.
func NewDate(data []byte) (date *Date, remainder []byte, err error) {
objdate, remainder, err := ReadDate(data)
date = &objdate
return
}

View File

@ -1,8 +1,9 @@
package common
package data
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTimeFromMiliseconds(t *testing.T) {

19
lib/common/data/errors.go Normal file
View File

@ -0,0 +1,19 @@
package data
import "fmt"
// WrapErrors compiles a slice of errors and returns them wrapped together as a single error.
func WrapErrors(errs []error) error {
var err error
for i, e := range errs {
err = fmt.Errorf("%v\n\t%d: %v", err, i, e)
}
return err
}
// PrintErrors prints a formatted list of errors to the console.
func PrintErrors(errs []error) {
for i, e := range errs {
fmt.Printf("\t%d: %v\n", i, e)
}
}

42
lib/common/data/hash.go Normal file
View File

@ -0,0 +1,42 @@
package data
import (
"crypto/sha256"
"io"
)
/*
[I2P Hash]
Accurate for version 0.9.49
Description
Represents the SHA256 of some data.
Contents
32 bytes
[I2P Hash]:
*/
// Hash is the represenation of an I2P Hash.
//
// https://geti2p.net/spec/common-structures#hash
type Hash [32]byte
// HashData returns the SHA256 sum of a []byte input as Hash.
func HashData(data []byte) (h Hash) {
h = sha256.Sum256(data)
return
}
// HashReader returns the SHA256 sum from all data read from an io.Reader.
// return error if one occurs while reading from reader
func HashReader(r io.Reader) (h Hash, err error) {
sha := sha256.New()
_, err = io.Copy(sha, r)
if err == nil {
d := sha.Sum(nil)
copy(h[:], d)
}
return
}

View File

@ -0,0 +1,85 @@
package data
import (
"encoding/binary"
)
// MAX_INTEGER_SIZE is the maximum length of an I2P integer in bytes.
const MAX_INTEGER_SIZE = 8
/*
[I2P Hash]
Accurate for version 0.9.49
Description
Represents a non-negative integer.
Contents
1 to 8 bytes in network byte order (big endian) representing an unsigned integer.
*/
// Integer is the represenation of an I2P Integer.
//
// https://geti2p.net/spec/common-structures#integer
type Integer []byte
// Bytes returns the raw []byte content of an Integer.
func (i Integer) Bytes() []byte {
return i[:]
}
// Int returns the Date as a Go integer
func (i Integer) Int() int {
return intFromBytes(i.Bytes())
}
// ReadInteger returns an Integer from a []byte of specified length.
// The remaining bytes after the specified length are also returned.
func ReadInteger(bytes []byte, size int) (Integer, []byte) {
if len(bytes) < size {
return bytes[:size], bytes[len(bytes):]
}
return bytes[:size], bytes[size:]
}
// NewInteger creates a new Integer from []byte using ReadInteger.
// Limits the length of the created Integer to MAX_INTEGER_SIZE.
// Returns a pointer to Integer unlike ReadInteger.
func NewInteger(bytes []byte, size int) (integer *Integer, remainder []byte, err error) {
integerSize := MAX_INTEGER_SIZE
if size < MAX_INTEGER_SIZE {
integerSize = size
}
intBytes := bytes[:integerSize]
remainder = bytes[integerSize:]
i, _ := ReadInteger(intBytes, integerSize)
integer = &i
return
}
// NewIntegerFromInt creates a new Integer from a Go integer of a specified []byte length.
func NewIntegerFromInt(value int, size int) (integer *Integer, err error) {
bytes := make([]byte, MAX_INTEGER_SIZE)
binary.BigEndian.PutUint64(bytes, uint64(value))
integerSize := MAX_INTEGER_SIZE
if size < MAX_INTEGER_SIZE {
integerSize = size
}
objinteger, _, err := NewInteger(bytes[MAX_INTEGER_SIZE-integerSize:], integerSize)
integer = objinteger
return
}
// Interpret a slice of bytes from length 0 to length 8 as a big-endian
// integer and return an int representation.
func intFromBytes(number []byte) (value int) {
num_len := len(number)
if num_len < MAX_INTEGER_SIZE {
number = append(
make([]byte, MAX_INTEGER_SIZE-num_len),
number...,
)
}
value = int(binary.BigEndian.Uint64(number))
return
}

View File

@ -1,8 +1,9 @@
package common
package data
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIntegerBigEndian(t *testing.T) {
@ -11,7 +12,7 @@ func TestIntegerBigEndian(t *testing.T) {
bytes := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
integer := Integer(bytes)
assert.Equal(integer, 1, "Integer() did not parse bytes big endian")
assert.Equal(integer.Int(), 1, "Integer() did not parse bytes big endian")
}
func TestWorksWithOneByte(t *testing.T) {
@ -19,7 +20,7 @@ func TestWorksWithOneByte(t *testing.T) {
integer := Integer([]byte{0x01})
assert.Equal(integer, 1, "Integer() did not correctly parse single byte slice")
assert.Equal(integer.Int(), 1, "Integer() did not correctly parse single byte slice")
}
func TestIsZeroWithNoData(t *testing.T) {
@ -27,5 +28,5 @@ func TestIsZeroWithNoData(t *testing.T) {
integer := Integer([]byte{})
assert.Equal(integer, 0, "Integer() did not correctly parse zero length byte slice")
assert.Equal(integer.Int(), 0, "Integer() did not correctly parse zero length byte slice")
}

180
lib/common/data/mapping.go Normal file
View File

@ -0,0 +1,180 @@
package data
import (
"errors"
log "github.com/sirupsen/logrus"
)
/*
[I2P Mapping]
Accurate for version 0.9.49
Description
A set of key/value mappings or properties
Contents
A 2-byte size Integer followed by a series of String=String; pairs
+----+----+----+----+----+----+----+----+
| size |key_string (len + data) | = |
+----+----+----+----+----+----+----+----+
| val_string (len + data) | ; | ...
+----+----+----+----+----+----+----+
size :: Integer
length -> 2 bytes
Total number of bytes that follow
key_string :: String
A string (one byte length followed by UTF-8 encoded characters)
= :: A single byte containing '='
val_string :: String
A string (one byte length followed by UTF-8 encoded characters)
; :: A single byte containing ';'
*/
// Mapping is the represenation of an I2P Mapping.
//
// https://geti2p.net/spec/common-structures#mapping
type Mapping struct {
size *Integer
vals *MappingValues
}
// Values returns the values contained in a Mapping as MappingValues.
func (mapping Mapping) Values() MappingValues {
if mapping.vals == nil {
return MappingValues{}
}
return *mapping.vals
}
// Data returns a Mapping in its []byte form.
func (mapping *Mapping) Data() []byte {
keyOrValIntegerLength := 1
bytes := mapping.size.Bytes()
for _, pair := range mapping.Values() {
klen, _ := pair[0].Length()
keylen, _ := NewIntegerFromInt(klen, keyOrValIntegerLength)
bytes = append(bytes, keylen.Bytes()...)
bytes = append(bytes, pair[0][1:]...)
bytes = append(bytes, 0x3d)
vlen, _ := pair[1].Length()
vallen, _ := NewIntegerFromInt(vlen, keyOrValIntegerLength)
bytes = append(bytes, vallen.Bytes()...)
bytes = append(bytes, pair[1][1:]...)
bytes = append(bytes, 0x3b)
}
return bytes
}
// HasDuplicateKeys returns true if two keys in a mapping are identical.
func (mapping *Mapping) HasDuplicateKeys() bool {
seen_values := make(map[string]bool)
values := mapping.Values()
for _, pair := range values {
key, _ := pair[0].Data()
if _, present := seen_values[key]; present {
return true
} else {
seen_values[key] = true
}
}
return false
}
// GoMapToMapping converts a Go map of unformatted strings to *Mapping.
func GoMapToMapping(gomap map[string]string) (mapping *Mapping, err error) {
map_vals := MappingValues{}
for k, v := range gomap {
key_str, kerr := ToI2PString(k)
if kerr != nil {
err = kerr
return
}
val_str, verr := ToI2PString(v)
if verr != nil {
err = verr
return
}
map_vals = append(
map_vals,
[2]I2PString{key_str, val_str},
)
}
mapping = ValuesToMapping(map_vals)
return
}
// Check if the string parsing error indicates that the Mapping
// should no longer be parsed.
func stopValueRead(err error) bool {
return err.Error() == "error parsing string: zero length"
}
// Determine if the first byte in a slice of bytes is the provided byte.
func beginsWith(bytes []byte, chr byte) bool {
return len(bytes) != 0 &&
bytes[0] == chr
}
// ReadMapping returns Mapping from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadMapping(bytes []byte) (mapping Mapping, remainder []byte, err []error) {
if len(bytes) < 3 {
log.WithFields(log.Fields{
"at": "ReadMapping",
"reason": "zero length",
}).Warn("mapping format violation")
e := errors.New("zero length")
err = append(err, e)
return
}
size, remainder, e := NewInteger(bytes, 2)
if e != nil {
err = append(err, e)
}
if size.Int() == 0 {
return
}
mapping.size = size
map_bytes := remainder[:mapping.size.Int()]
remainder = remainder[mapping.size.Int():]
if len(remainder) == 0 {
log.WithFields(log.Fields{
"at": "ReadMapping",
"reason": "zero length",
}).Warn("mapping format violation")
e := errors.New("zero length")
err = append(err, e)
}
// TODO: this should take the remainder and the length we already parsed above, as a parameter.
// Like tomorrow morning.
// ReadMappingValues should not attempt to figure out the length of the bytes it's reading over.
vals, _, mappingValueErrs := ReadMappingValues(map_bytes, *mapping.size)
err = append(err, mappingValueErrs...)
mapping.vals = vals
if len(mappingValueErrs) > 0 {
log.WithFields(log.Fields{
"at": "ReadMapping",
"reason": "error parsing mapping values",
}).Warn("mapping format violation")
e := errors.New("error parsing mapping values")
err = append(err, e)
}
return
}
// NewMapping creates a new *Mapping from []byte using ReadMapping.
// Returns a pointer to Mapping unlike ReadMapping.
func NewMapping(bytes []byte) (values *Mapping, remainder []byte, err []error) {
objvalues, remainder, err := ReadMapping(bytes)
values = &objvalues
return
}

View File

@ -0,0 +1,193 @@
package data
import (
"bytes"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestValuesExclusesPairWithBadData(t *testing.T) {
assert := assert.New(t)
bad_key, _, errs := NewMapping([]byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x00})
values := bad_key.Values()
e := WrapErrors(errs)
t.Log(e)
assert.NotNil(errs, "Values() did not return errors when some values had bad key")
if assert.Equal(1, len(values), "Values() did not return valid values when some values had bad key") {
k := values[0][0]
key, _ := k.Data()
v := values[0][1]
val, _ := v.Data()
assert.Equal(key, "a", "Values() returned by data with invalid key contains incorrect present key")
assert.Equal(val, "b", "Values() returned by data with invalid key contains incorrect present key")
}
}
func TestValuesWarnsMissingData(t *testing.T) {
assert := assert.New(t)
_, _, errs := NewMapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62})
if assert.Equal(2, len(errs), "Values() reported wrong error count when mapping had missing data") {
assert.Equal(errs[0].Error(), "warning parsing mapping: mapping length exceeds provided data")
}
}
func TestValuesWarnsExtraData(t *testing.T) {
assert := assert.New(t)
mapping, _, errs := NewMapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x00})
values := mapping.Values()
key, kerr := values[0][0].Data()
val, verr := values[0][1].Data()
assert.Nil(kerr)
assert.Nil(verr)
assert.Equal(key, "a", "Values() did not return key in valid data")
assert.Equal(val, "b", "Values() did not return value in valid data")
if assert.Equal(2, len(errs), "Values() reported wrong error count when mapping had extra data") {
assert.Equal("warning parsing mapping: data exists beyond length of mapping", errs[0].Error(), "correct error message should be returned")
}
}
func TestValuesEnforcesEqualDelimitor(t *testing.T) {
assert := assert.New(t)
mapping, _, errs := NewMapping([]byte{0x00, 0x06, 0x01, 0x61, 0x30, 0x01, 0x62, 0x3b})
values := mapping.Values()
if assert.Equal(2, len(errs), "Values() reported wrong error count when mapping had = format error") {
assert.Equal("mapping format violation, expected =", errs[0].Error(), "correct error message should be returned")
}
assert.Equal(0, len(values), "Values() not empty with invalid data due to = format error")
}
func TestValuesEnforcedSemicolonDelimitor(t *testing.T) {
assert := assert.New(t)
mapping, _, errs := NewMapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x30})
values := mapping.Values()
if assert.Equal(2, len(errs), "Values() reported wrong error count when mapping had ; format error") {
assert.Equal("mapping format violation, expected ;", errs[0].Error(), "correct error message should be returned")
}
assert.Equal(0, len(values), "Values() not empty with invalid data due to ; format error")
}
func TestValuesReturnsValues(t *testing.T) {
assert := assert.New(t)
mapping, _, errs := NewMapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b})
values := mapping.Values()
key, kerr := values[0][0].Data()
val, verr := values[0][1].Data()
assert.Nil(errs, "Values() returned a errors with parsing valid data")
assert.Nil(kerr)
assert.Nil(verr)
assert.Equal("a", key, "Values() did not return key in valid data")
assert.Equal("b", val, "Values() did not return value in valid data")
}
func TestHasDuplicateKeysTrueWhenDuplicates(t *testing.T) {
assert := assert.New(t)
dups, _, _ := NewMapping([]byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b})
assert.Equal(true, dups.HasDuplicateKeys(), "HasDuplicateKeys() did not report true when duplicate keys present")
}
func TestHasDuplicateKeysFalseWithoutDuplicates(t *testing.T) {
assert := assert.New(t)
mapping, _, _ := NewMapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b})
assert.Equal(false, mapping.HasDuplicateKeys(), "HasDuplicateKeys() did not report false when no duplicate keys present")
}
func TestReadMappingHasDuplicateKeys(t *testing.T) {
assert := assert.New(t)
_, _, errs := NewMapping([]byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b})
assert.Equal("mapping format violation, duplicate key in mapping", errs[0].Error(), "ReadMapping should throw an error when duplicate keys are present.")
}
func TestGoMapToMappingProducesCorrectMapping(t *testing.T) {
assert := assert.New(t)
gomap := map[string]string{"a": "b"}
mapping, err := GoMapToMapping(gomap)
assert.Nil(err, "GoMapToMapping() returned error with valid data")
expected := []byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b}
if bytes.Compare(mapping.Data(), expected) != 0 {
t.Fatal("GoMapToMapping did not produce correct Mapping", mapping, expected)
}
}
func TestFullGoMapToMappingProducesCorrectMapping(t *testing.T) {
assert := assert.New(t)
gomap := map[string]string{
"a": "b",
"c": "d",
}
mapping, err := GoMapToMapping(gomap)
assert.Nil(err, "GoMapToMapping() returned error with valid data")
expected := []byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x01, 0x63, 0x3d, 0x01, 0x64, 0x3b}
if bytes.Compare(mapping.Data(), expected) != 0 {
t.Fatal("GoMapToMapping did not produce correct Mapping", mapping, expected)
}
}
func TestStopValueReadTrueWhenCorrectErr(t *testing.T) {
assert := assert.New(t)
status := stopValueRead(errors.New("error parsing string: zero length"))
assert.Equal(true, status, "stopValueRead() did not return true when String error found")
}
func TestStopValueReadFalseWhenWrongErr(t *testing.T) {
assert := assert.New(t)
status := stopValueRead(errors.New("something else"))
assert.Equal(false, status, "stopValueRead() did not return false when non String error found")
}
func TestBeginsWithCorrectWhenTrue(t *testing.T) {
assert := assert.New(t)
slice := []byte{0x41}
assert.Equal(true, beginsWith(slice, 0x41), "beginsWith() did not return true when correct")
}
func TestBeginsWithCorrectWhenFalse(t *testing.T) {
assert := assert.New(t)
slice := []byte{0x00}
assert.Equal(false, beginsWith(slice, 0x41), "beginsWith() did not false when incorrect")
}
func TestBeginsWithCorrectWhenNil(t *testing.T) {
assert := assert.New(t)
slice := make([]byte, 0)
assert.Equal(false, beginsWith(slice, 0x41), "beginsWith() did not return false on empty slice")
}

View File

@ -0,0 +1,194 @@
package data
import (
"errors"
"sort"
log "github.com/sirupsen/logrus"
)
// MappingValues represents the parsed key value pairs inside of an I2P Mapping.
type MappingValues [][2]I2PString
func (m MappingValues) Get(key I2PString) I2PString {
keyBytes, _ := key.Data()
for _, pair := range m {
kb, _ := pair[0][0:].Data()
if kb == keyBytes {
return pair[1][1:]
}
}
return nil
}
// ValuesToMapping creates a *Mapping using MappingValues.
// The values are sorted in the order defined in mappingOrder.
func ValuesToMapping(values MappingValues) *Mapping {
// Default length to 2 * len
// 1 byte for ;
// 1 byte for =
baseLength := 2 * len(values)
for _, mappingVals := range values {
for _, keyOrVal := range mappingVals {
baseLength += len(keyOrVal)
}
}
mappingSize, _ := NewIntegerFromInt(baseLength, 2)
return &Mapping{
size: mappingSize,
vals: &values,
}
}
// I2P Mappings require consistent order in some cases for cryptographic signing, and sorting
// by keys. The Mapping is sorted lexographically by keys. Duplicate keys are allowed in general,
// but in implementations where they must be sorted like I2CP SessionConfig duplicate keys are not allowed.
// In practice routers do not seem to allow duplicate keys.
func mappingOrder(values MappingValues) {
sort.SliceStable(values, func(i, j int) bool {
// Lexographic sort on keys only
data1, _ := values[i][0].Data()
data2, _ := values[j][0].Data()
return data1 < data2
})
}
// ReadMappingValues returns *MappingValues from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadMappingValues(remainder []byte, map_length Integer) (values *MappingValues, remainder_bytes []byte, errs []error) {
//mapping := remainder
//var remainder = mapping
//var err error
if remainder == nil || len(remainder) < 1 {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"reason": "data shorter than expected",
}).Error("mapping contained no data")
errs = []error{errors.New("mapping contained no data")}
return
}
map_values := make(MappingValues, 0)
int_map_length := map_length.Int()
mapping_len := len(remainder)
if mapping_len > int_map_length {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"mapping_bytes_length": mapping_len,
"mapping_length_field": int_map_length,
"reason": "data longer than expected",
}).Warn("mapping format warning")
errs = append(errs, errors.New("warning parsing mapping: data exists beyond length of mapping"))
} else if int_map_length > mapping_len {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"mapping_bytes_length": mapping_len,
"mapping_length_field": int_map_length,
"reason": "data shorter than expected",
}).Warn("mapping format warning")
errs = append(errs, errors.New("warning parsing mapping: mapping length exceeds provided data"))
}
encounteredKeysMap := map[string]bool{}
// pop off length bytes before parsing kv pairs
//remainder = remainder[2:]
for {
// Read a key, breaking on fatal errors
// and appending warnings
// Minimum byte length required for another KV pair.
// Two bytes for each string length
// At least 1 byte per string
// One byte for =
// One byte for ;
if len(remainder) < 6 {
// Not returning an error here as the issue is already flagged by mapping length being wrong.
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"reason": "mapping format violation",
}).Warn("mapping format violation, too few bytes for a kv pair")
break
}
key_str, more, err := ReadI2PString(remainder)
if err != nil {
if stopValueRead(err) {
errs = append(errs, err)
//return
}
}
// overwriting remainder with more as another var to prevent memory weirdness in loops
remainder = more
//log.Printf("(MAPPING VALUES DEBUG) Remainder: %s\n", remainder)
// Check if key has already been encountered in this mapping
keyBytes, _ := key_str.Data()
keyAsString := string(keyBytes)
_, ok := encounteredKeysMap[keyAsString]
if ok {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"reason": "duplicate key in mapping",
"key": string(key_str),
}).Error("mapping format violation")
log.Printf("DUPE: %s", key_str)
errs = append(errs, errors.New("mapping format violation, duplicate key in mapping"))
// Based on other implementations this does not seem to happen often?
// Java throws an exception in this case, the base object is a Hashmap so the value is overwritten and an exception is thrown.
// i2pd as far as I can tell just overwrites the original value
// Continue on, we can check if the Mapping contains duplicate keys later.
}
if !beginsWith(remainder, 0x3d) {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"reason": "expected =",
"value:": string(remainder),
}).Warn("mapping format violation")
errs = append(errs, errors.New("mapping format violation, expected ="))
log.Printf("ERRVAL: %s", remainder)
break
} else {
remainder = remainder[1:]
}
// Read a value, breaking on fatal errors
// and appending warnings
val_str, more, err := ReadI2PString(remainder)
if err != nil {
if stopValueRead(err) {
errs = append(errs, err)
//return
}
}
// overwriting remainder with more as another var to prevent memory weirdness in loops
remainder = more
//log.Printf("(MAPPING VALUES DEBUG) Remainder: %s\n", remainder)
//log.Printf("(MAPPING VALUES DEBUG) String: value: %s", val_str)
if !beginsWith(remainder, 0x3b) {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"reason": "expected ;",
"value:": string(remainder),
}).Warn("mapping format violation")
errs = append(errs, errors.New("mapping format violation, expected ;"))
break
} else {
remainder = remainder[1:]
}
// Append the key-value pair and break if there is no more data to read
map_values = append(map_values, [2]I2PString{key_str, val_str})
if len(remainder) == 0 {
break
}
// Store the encountered key with arbitrary data
encounteredKeysMap[keyAsString] = true
}
values = &map_values
return
}

View File

@ -0,0 +1,47 @@
package data
import (
"fmt"
"testing"
)
func TestMappingOrderSortsValuesThenKeys(t *testing.T) {
a, _ := ToI2PString("a")
b, _ := ToI2PString("b")
aa, _ := ToI2PString("aa")
ab, _ := ToI2PString("ab")
ac, _ := ToI2PString("ac")
values := MappingValues{
[2]I2PString{b, b},
[2]I2PString{ac, a},
[2]I2PString{ab, b},
[2]I2PString{aa, a},
[2]I2PString{a, a},
}
mappingOrder(values)
for i, pair := range values {
key, _ := pair[0].Data()
switch i {
case 0:
if !(key == "a") {
t.Fatal(fmt.Sprintf("mappingOrder expected key a, got %s at index", key), i)
}
case 1:
if !(key == "aa") {
t.Fatal(fmt.Sprintf("mappingOrder expected key aa, got %s at index", key), i)
}
case 2:
if !(key == "ab") {
t.Fatal(fmt.Sprintf("mappingOrder expected key ab, got %s at index", key), i)
}
case 3:
if !(key == "ac") {
t.Fatal(fmt.Sprintf("mappingOrder expected key ac, got %s at index", key), i)
}
case 4:
if !(key == "b") {
t.Fatal(fmt.Sprintf("mappingOrder expected key b, got %s at index", key), i)
}
}
}
}

125
lib/common/data/string.go Normal file
View File

@ -0,0 +1,125 @@
package data
import (
"errors"
log "github.com/sirupsen/logrus"
)
// STRING_MAX_SIZE is the maximum number of bytes that can be stored in an I2P string
const STRING_MAX_SIZE = 255
/*
[I2P String]
Accurate for version 0.9.49
Description
Represents a UTF-8 encoded string.
Contents
1 or more bytes where the first byte is the number of bytes (not characters!) in the string
and the remaining 0-255 bytes are the non-null terminated UTF-8 encoded character array.
Length limit is 255 bytes (not characters). Length may be 0.
*/
// I2PString is the represenation of an I2P String.
//
// https://geti2p.net/spec/common-structures#string
type I2PString []byte
// Length returns the length specified in the first byte.
// Returns error if the specified does not match the actual length or the string is otherwise invalid.
func (str I2PString) Length() (length int, err error) {
if len(str) == 0 {
log.WithFields(log.Fields{
"at": "(I2PString) Length",
"reason": "no data",
}).Error("error parsing string")
err = errors.New("error parsing string: zero length")
return
}
l, _, _ := NewInteger(str, 1)
length = l.Int()
str_len := len(str) - 1
if length != str_len {
log.WithFields(log.Fields{
"at": "(I2PString) Length",
"string_bytes_length": str_len,
"string_length_field": length,
"reason": "data less than specified by length",
}).Error("string format warning")
err = errors.New("string parsing warning: string data is shorter than specified by length")
}
return
}
// Data returns the I2PString content as a string trimmed to the specified length and not including the length byte.
// Returns error encountered by Length.
func (str I2PString) Data() (data string, err error) {
length, err := str.Length()
if err != nil {
switch err.Error() {
case "error parsing string: zero length":
return
case "string parsing warning: string data is shorter than specified by length":
data = string(str[1:])
return
case "string parsing warning: string contains data beyond length":
data = string(str[1:])
return
}
}
if length == 0 {
return
}
data = string(str[1 : length+1])
return
}
// ToI2PString converts a Go string to an I2PString.
// Returns error if the string exceeds STRING_MAX_SIZE.
func ToI2PString(data string) (str I2PString, err error) {
data_len := len(data)
if data_len > STRING_MAX_SIZE {
log.WithFields(log.Fields{
"at": "ToI2PI2PString",
"string_len": data_len,
"max_len": STRING_MAX_SIZE,
"reason": "too much data",
}).Error("cannot create I2P string")
err = errors.New("cannot store that much data in I2P string")
return
}
i2p_string := []byte{byte(data_len)}
i2p_string = append(i2p_string, []byte(data)...)
str = I2PString(i2p_string)
return
}
//
// Read a string from a slice of bytes, returning any extra data on the end
// of the slice and any errors encountered parsing the I2PString.
//
// ReadI2PString returns I2PString from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadI2PString(data []byte) (str I2PString, remainder []byte, err error) {
length, _, err := NewInteger(data, 1)
if err != nil {
return
}
data_len := length.Int()
str = data[:data_len+1]
remainder = data[data_len+1:]
_, err = str.Length()
return
}
// NewI2PString creates a new *I2PString from []byte using ReadI2PString.
// Returns a pointer to I2PString unlike ReadI2PString.
func NewI2PString(data []byte) (str *I2PString, remainder []byte, err error) {
objstr, remainder, err := ReadI2PString(data)
str = &objstr
return
}

View File

@ -1,23 +1,24 @@
package common
package data
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStringReportsCorrectLength(t *testing.T) {
assert := assert.New(t)
str_len, err := String([]byte{0x02, 0x00, 0x00}).Length()
str_len, err := I2PString([]byte{0x02, 0x00, 0x00}).Length()
assert.Equal(str_len, 2, "Length() did not report correct length")
assert.Nil(err, "Length() reported an error on valid string")
}
func TestStringReportsLengthZeroError(t *testing.T) {
func TestI2PStringReportsLengthZeroError(t *testing.T) {
assert := assert.New(t)
str_len, err := String(make([]byte, 0)).Length()
str_len, err := I2PString(make([]byte, 0)).Length()
assert.Equal(str_len, 0, "Length() reported non-zero length on empty slice")
if assert.NotNil(err) {
@ -25,10 +26,10 @@ func TestStringReportsLengthZeroError(t *testing.T) {
}
}
func TestStringReportsExtraDataError(t *testing.T) {
func TestI2PStringReportsExtraDataError(t *testing.T) {
assert := assert.New(t)
str_len, err := String([]byte{0x01, 0x00, 0x00}).Length()
str_len, err := I2PString([]byte{0x01, 0x00, 0x00}).Length()
assert.Equal(str_len, 1, "Length() reported wrong size when extra data present")
if assert.NotNil(err) {
@ -36,10 +37,10 @@ func TestStringReportsExtraDataError(t *testing.T) {
}
}
func TestStringDataReportsLengthZeroError(t *testing.T) {
func TestI2PStringDataReportsLengthZeroError(t *testing.T) {
assert := assert.New(t)
str_len, err := String([]byte{0x01}).Length()
str_len, err := I2PString([]byte{0x01}).Length()
assert.Equal(str_len, 1, "Length() reported wrong size with missing data")
if assert.NotNil(err) {
@ -47,10 +48,10 @@ func TestStringDataReportsLengthZeroError(t *testing.T) {
}
}
func TestStringDataReportsExtraDataError(t *testing.T) {
func TestI2PStringDataReportsExtraDataError(t *testing.T) {
assert := assert.New(t)
data, err := String([]byte{0x01, 0x00, 0x01}).Data()
data, err := I2PString([]byte{0x01, 0x00, 0x01}).Data()
data_len := len(data)
assert.Equal(data_len, 1, "Data() reported wrong size on string with extra data")
@ -59,10 +60,10 @@ func TestStringDataReportsExtraDataError(t *testing.T) {
}
}
func TestStringDataEmptyWhenZeroLength(t *testing.T) {
func TestI2PStringDataEmptyWhenZeroLength(t *testing.T) {
assert := assert.New(t)
data, err := String(make([]byte, 0)).Data()
data, err := I2PString(make([]byte, 0)).Data()
assert.Equal(len(data), 0, "Data() returned data when none was present:")
if assert.NotNil(err) {
@ -70,10 +71,10 @@ func TestStringDataEmptyWhenZeroLength(t *testing.T) {
}
}
func TestStringDataErrorWhenNonZeroLengthOnly(t *testing.T) {
func TestI2PStringDataErrorWhenNonZeroLengthOnly(t *testing.T) {
assert := assert.New(t)
data, err := String([]byte{0x01}).Data()
data, err := I2PString([]byte{0x01}).Data()
assert.Equal(len(data), 0, "Data() returned data when only length was present")
if assert.NotNil(err) {
@ -81,7 +82,7 @@ func TestStringDataErrorWhenNonZeroLengthOnly(t *testing.T) {
}
}
func TestToI2PStringFormatsCorrectly(t *testing.T) {
func TestToI2PI2PStringFormatsCorrectly(t *testing.T) {
assert := assert.New(t)
i2p_string, err := ToI2PString(string([]byte{0x08, 0x09}))
@ -111,38 +112,38 @@ func TestReadStringReadsLength(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x01, 0x04, 0x06}
str, remainder, err := ReadString(bytes)
str, remainder, err := ReadI2PString(bytes)
assert.Nil(err, "ReadString() returned error reading string with extra data")
assert.Equal(len(str), 2, "ReadString() did not return correct string length")
assert.Equal(1, int(str[0]), "ReadString() did not return correct string")
assert.Equal(4, int(str[1]), "ReadString() did not return correct string")
assert.Equal(len(remainder), 1, "ReadString() did not return correct remainder length")
assert.Equal(6, int(remainder[0]), "ReadString() did not return correct remainder")
assert.Nil(err, "ReadI2PString() returned error reading string with extra data")
assert.Equal(len(str), 2, "ReadI2PString() did not return correct string length")
assert.Equal(1, int(str[0]), "ReadI2PString() did not return correct string")
assert.Equal(4, int(str[1]), "ReadI2PString() did not return correct string")
assert.Equal(len(remainder), 1, "ReadI2PString() did not return correct remainder length")
assert.Equal(6, int(remainder[0]), "ReadI2PString() did not return correct remainder")
}
func TestReadStringErrWhenEmptySlice(t *testing.T) {
func TestReadI2PStringErrWhenEmptySlice(t *testing.T) {
assert := assert.New(t)
bytes := make([]byte, 0)
_, _, err := ReadString(bytes)
_, _, err := ReadI2PString(bytes)
if assert.NotNil(err) {
assert.Equal(err.Error(), "error parsing string: zero length", "correct error message should be returned")
}
}
func TestReadStringErrWhenDataTooShort(t *testing.T) {
func TestReadI2PStringErrWhenDataTooShort(t *testing.T) {
assert := assert.New(t)
short_str := []byte{0x03, 0x01}
str, remainder, err := ReadString(short_str)
str, remainder, err := ReadI2PString(short_str)
if assert.NotNil(err) {
assert.Equal(err.Error(), "string parsing warning: string data is shorter than specified by length", "correct error message should be returned")
}
assert.Equal(len(str), 2, "ReadString() did not return the slice as string when too long")
assert.Equal(3, int(str[0]), "ReadString() did not return the correct partial string")
assert.Equal(1, int(str[1]), "ReadString() did not return the correct partial string")
assert.Equal(len(remainder), 0, "ReadString() returned a remainder when the string data was too short")
assert.Equal(len(str), 2, "ReadI2PString() did not return the slice as string when too long")
assert.Equal(3, int(str[0]), "ReadI2PString() did not return the correct partial string")
assert.Equal(1, int(str[1]), "ReadI2PString() did not return the correct partial string")
assert.Equal(len(remainder), 0, "ReadI2PString() returned a remainder when the string data was too short")
}

View File

@ -1,24 +0,0 @@
package common
/*
I2P Date
https://geti2p.net/spec/common-structures#date
Accurate for version 0.9.24
*/
import (
"time"
)
type Date [8]byte
//
// Time takes the value stored in date as an 8 byte big-endian integer representing the
// number of milliseconds since the beginning of unix time and converts it to a Go time.Time
// struct.
//
func (date Date) Time() (date_time time.Time) {
seconds := Integer(date[:])
date_time = time.Unix(0, int64(seconds*1000000))
return
}

View File

@ -1,57 +0,0 @@
package common
/*
I2P Destination
https://geti2p.net/spec/common-structures#destination
Accurate for version 0.9.24
Identical to KeysAndCert
*/
import (
"github.com/go-i2p/go-i2p/lib/common/base32"
"github.com/go-i2p/go-i2p/lib/common/base64"
"github.com/go-i2p/go-i2p/lib/crypto"
"strings"
)
//
// A Destination is a KeysAndCert with functionallity
// for generating base32 and base64 addresses.
//
type Destination []byte
func (destination Destination) PublicKey() (crypto.PublicKey, error) {
return KeysAndCert(destination).PublicKey()
}
func (destination Destination) SigningPublicKey() (crypto.SigningPublicKey, error) {
return KeysAndCert(destination).SigningPublicKey()
}
func (destination Destination) Certificate() (Certificate, error) {
return KeysAndCert(destination).Certificate()
}
func ReadDestination(data []byte) (destination Destination, remainder []byte, err error) {
keys_and_cert, remainder, err := ReadKeysAndCert(data)
destination = Destination(keys_and_cert)
return
}
//
// Generate the I2P base32 address for this Destination.
//
func (destination Destination) Base32Address() (str string) {
hash := crypto.SHA256(destination)
str = strings.Trim(base32.EncodeToString(hash[:]), "=")
str = str + ".b32.i2p"
return
}
//
// Generate the I2P base64 address for this Destination.
//
func (destination Destination) Base64() string {
return base64.EncodeToString(destination)
}

View File

@ -0,0 +1,64 @@
// Package destination implements the I2P Destination common data structure
package destination
import (
"strings"
. "github.com/go-i2p/go-i2p/lib/common/keys_and_cert"
"github.com/go-i2p/go-i2p/lib/common/base32"
"github.com/go-i2p/go-i2p/lib/common/base64"
"github.com/go-i2p/go-i2p/lib/crypto"
)
/*
[Destination]
Accurate for version 0.9.49
Description
A Destination defines a particular endpoint to which messages can be directed for secure delivery.
Contents
Identical to KeysAndCert.
*/
// Destination is the represenation of an I2P Destination.
//
// https://geti2p.net/spec/common-structures#destination
type Destination struct {
*KeysAndCert
}
// Base32Address returns the I2P base32 address for this Destination.
func (destination Destination) Base32Address() (str string) {
dest := destination.KeysAndCert.KeyCertificate.Bytes()
hash := crypto.SHA256(dest)
str = strings.Trim(base32.EncodeToString(hash[:]), "=")
str = str + ".b32.i2p"
return
}
// Base64 returns the I2P base64 address for this Destination.
func (destination Destination) Base64() string {
dest := destination.KeysAndCert.KeyCertificate.Bytes()
return base64.EncodeToString(dest)
}
// ReadDestination returns Destination from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadDestination(data []byte) (destination Destination, remainder []byte, err error) {
keys_and_cert, remainder, err := NewKeysAndCert(data)
destination = Destination{
keys_and_cert,
}
return
}
// NewDestination creates a new *Destination from []byte using ReadDestination.
// Returns a pointer to Destination unlike ReadDestination.
func NewDestination(data []byte) (destination *Destination, remainder []byte, err error) {
objdestination, remainder, err := ReadDestination(data)
destination = &objdestination
return destination, remainder, err
}

View File

@ -1,8 +1,7 @@
FROM golang
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install libsodium-dev -y
apt-get upgrade -y
RUN go get github.com/dvyukov/go-fuzz/go-fuzz
RUN go get github.com/dvyukov/go-fuzz/go-fuzz-build

View File

@ -1,9 +1,9 @@
package exportable
import "github.com/go-i2p/go-i2p/lib/common"
import common "github.com/go-i2p/go-i2p/lib/common/certificate"
func Fuzz(data []byte) int {
cert := common.Certificate(data)
cert, _, _ := common.ReadCertificate(data)
cert.Data()
cert.Length()
cert.Type()

View File

@ -1,9 +1,9 @@
package exportable
import "github.com/go-i2p/go-i2p/lib/common"
import common "github.com/go-i2p/go-i2p/lib/common/destination"
func Fuzz(data []byte) int {
destination := common.Destination(data)
destination, _, _ := common.ReadDestination(data)
destination.Base32Address()
destination.Base64()
return 0

View File

@ -1,9 +1,9 @@
package exportable
import "github.com/go-i2p/go-i2p/lib/common"
import common "github.com/go-i2p/go-i2p/lib/common/keys_and_cert"
func Fuzz(data []byte) int {
keys_and_cert, _, _ := common.ReadKeysAndCert(data)
keys_and_cert, _, _ := common.NewKeysAndCert(data)
keys_and_cert.Certificate()
keys_and_cert.PublicKey()
keys_and_cert.SigningPublicKey()

View File

@ -1,6 +1,6 @@
package exportable
import "github.com/go-i2p/go-i2p/lib/common"
import common "github.com/go-i2p/go-i2p/lib/common/router_address"
func Fuzz(data []byte) int {
router_address, _, _ := common.ReadRouterAddress(data)

View File

@ -1,6 +1,6 @@
package exportable
import "github.com/go-i2p/go-i2p/lib/common"
import common "github.com/go-i2p/go-i2p/lib/common/router_identity"
func Fuzz(data []byte) int {
router_identity, _, _ := common.ReadRouterIdentity(data)

View File

@ -1,9 +1,9 @@
package exportable
import "github.com/go-i2p/go-i2p/lib/common"
import common "github.com/go-i2p/go-i2p/lib/common/data"
func Fuzz(data []byte) int {
str, _, _ := common.ReadString(data)
str := common.I2PString(data)
str.Data()
str.Length()
str, _ = common.ToI2PString(string(data))

View File

@ -1,27 +0,0 @@
package common
import (
"crypto/sha256"
"io"
)
// sha256 hash of some data
type Hash [32]byte
// calculate sha256 of a byte slice
func HashData(data []byte) (h Hash) {
h = sha256.Sum256(data)
return
}
// calulate sha256 of all data being read from an io.Reader
// return error if one occurs while reading from reader
func HashReader(r io.Reader) (h Hash, err error) {
sha := sha256.New()
_, err = io.Copy(sha, r)
if err == nil {
d := sha.Sum(nil)
copy(h[:], d)
}
return
}

View File

@ -1,32 +0,0 @@
package common
/*
I2P Integer
https://geti2p.net/spec/common-structures#integer
Accurate for version 0.9.24
*/
import (
"encoding/binary"
)
// Total byte length of an I2P integer
const (
INTEGER_SIZE = 8
)
//
// Interpret a slice of bytes from length 0 to length 8 as a big-endian
// integer and return an int representation.
//
func Integer(number []byte) (value int) {
num_len := len(number)
if num_len < INTEGER_SIZE {
number = append(
make([]byte, INTEGER_SIZE-num_len),
number...,
)
}
value = int(binary.BigEndian.Uint64(number))
return
}

View File

@ -1,4 +1,5 @@
package common
// Package key_certificate implements the I2P Destination common data structure
package key_certificate
/*
I2P Key Certificate
@ -28,6 +29,9 @@ payload :: data
import (
"errors"
. "github.com/go-i2p/go-i2p/lib/common/certificate"
. "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/crypto"
log "github.com/sirupsen/logrus"
)
@ -48,6 +52,14 @@ const (
// Key Certificate Public Key Types
const (
KEYCERT_CRYPTO_ELG = iota
KEYCERT_CRYPTO_P256
KEYCERT_CRYPTO_P384
KEYCERT_CRYPTO_P521
KEYCERT_CRYPTO_X25519
)
const (
KEYCERT_MIN_SIZE = 7
)
// SigningPublicKey sizes for Signing Key Types
@ -65,7 +77,11 @@ const (
// PublicKey sizes for Public Key Types
const (
KEYCERT_CRYPTO_ELG_SIZE = 256
KEYCERT_CRYPTO_ELG_SIZE = 256
KEYCERT_CRYPTO_P256_SIZE = 64
KEYCERT_CRYPTO_P384_SIZE = 96
KEYCERT_CRYPTO_P521_SIZE = 132
KEYCERT_CRYPTO_X25519_SIZE = 32
)
// Sizes of structures in KeyCertificates
@ -74,78 +90,37 @@ const (
KEYCERT_SPK_SIZE = 128
)
type KeyCertificate []byte
// type KeyCertificate []byte
type KeyCertificate struct {
*Certificate
spkType Integer
cpkType Integer
}
//
// The data contained in the Key Certificate.
//
// Data returns the raw []byte contained in the Certificate.
func (key_certificate KeyCertificate) Data() ([]byte, error) {
return Certificate(key_certificate).Data()
return key_certificate.Certificate.RawBytes(), nil
}
//
// The SigningPublicKey type this Key Certificate describes and any errors encountered
// parsing the KeyCertificate.
//
func (key_certificate KeyCertificate) SigningPublicKeyType() (signing_pubkey_type int, err error) {
data, err := key_certificate.Data()
if err != nil {
log.WithFields(log.Fields{
"at": "(KeyCertificate) SigningPublicKeyType",
"reason": err.Error(),
}).Error("error getting signing public key")
return
}
data_len := len(data)
if data_len < 2 {
log.WithFields(log.Fields{
"at": "(KeyCertificate) SigningPublicKeyType",
"data_len": data_len,
"required_len": 2,
"reason": "not enough data",
}).Error("error parsing key certificate")
err = errors.New("error parsing key certificate: not enough data")
return
}
signing_pubkey_type = Integer(data[:2])
return
// SigningPublicKeyType returns the SigningPublicKey type as a Go integer.
func (key_certificate KeyCertificate) SigningPublicKeyType() (signing_pubkey_type int) {
return key_certificate.spkType.Int()
}
//
// The PublicKey type this Key Certificate describes and any errors encountered parsing
// this KeyCertificate.
//
func (key_certificate KeyCertificate) PublicKeyType() (pubkey_type int, err error) {
data, err := key_certificate.Data()
if err != nil {
return
}
data_len := len(data)
if data_len < 4 {
log.WithFields(log.Fields{
"at": "(KeyCertificate) PublicKeyType",
"data_len": data_len,
"required_len": 4,
"reason": "not enough data",
}).Error("error parsing key certificate")
err = errors.New("error parsing key certificate: not enough data")
return
}
pubkey_type = Integer(data[2:4])
return
// PublicKeyType returns the PublicKey type as a Go integer.
func (key_certificate KeyCertificate) PublicKeyType() (pubkey_type int) {
return key_certificate.cpkType.Int()
}
//
// Given some bytes, build a PublicKey using any excess data that may be stored in the KeyCertificate and return
// it along with any errors encountered constructing the PublicKey.
//
// ConstructPublicKey returns a PublicKey constructed using any excess data that may be stored in the KeyCertififcate.
// Returns enr errors encountered while parsing.
func (key_certificate KeyCertificate) ConstructPublicKey(data []byte) (public_key crypto.PublicKey, err error) {
key_type, err := key_certificate.PublicKeyType()
key_type := key_certificate.PublicKeyType()
if err != nil {
return
}
data_len := len(data)
if data_len < KEYCERT_PUBKEY_SIZE {
if data_len < key_certificate.CryptoSize() {
log.WithFields(log.Fields{
"at": "(KeyCertificate) ConstructPublicKey",
"data_len": data_len,
@ -160,21 +135,23 @@ func (key_certificate KeyCertificate) ConstructPublicKey(data []byte) (public_ke
var elg_key crypto.ElgPublicKey
copy(elg_key[:], data[KEYCERT_PUBKEY_SIZE-KEYCERT_CRYPTO_ELG_SIZE:KEYCERT_PUBKEY_SIZE])
public_key = elg_key
case KEYCERT_CRYPTO_X25519:
var ed25519_key crypto.Ed25519PublicKey
copy(ed25519_key[:], data[KEYCERT_PUBKEY_SIZE-KEYCERT_CRYPTO_ELG_SIZE:KEYCERT_PUBKEY_SIZE])
public_key = ed25519_key
}
return
}
//
// Given some bytes, build a SigningPublicKey using any excess data that may be stored in the KeyCertificate and return
// it along with any errors encountered constructing the SigningPublicKey.
//
// ConstructSigningPublicKey returns a SingingPublicKey constructed using any excess data that may be stored in the KeyCertificate.
// Returns any errors encountered while parsing.
func (key_certificate KeyCertificate) ConstructSigningPublicKey(data []byte) (signing_public_key crypto.SigningPublicKey, err error) {
signing_key_type, err := key_certificate.PublicKeyType()
signing_key_type := key_certificate.PublicKeyType()
if err != nil {
return
}
data_len := len(data)
if data_len < KEYCERT_SPK_SIZE {
if data_len < key_certificate.SignatureSize() {
log.WithFields(log.Fields{
"at": "(KeyCertificate) ConstructSigningPublicKey",
"data_len": data_len,
@ -201,7 +178,7 @@ func (key_certificate KeyCertificate) ConstructSigningPublicKey(data []byte) (si
var ec_key crypto.ECP521PublicKey
extra := KEYCERT_SIGN_P521_SIZE - KEYCERT_SPK_SIZE
copy(ec_key[:], data)
copy(ec_key[KEYCERT_SPK_SIZE:], key_certificate[4:4+extra])
copy(ec_key[KEYCERT_SPK_SIZE:], key_certificate.Certificate.RawBytes()[4:4+extra])
signing_public_key = ec_key
case KEYCERT_SIGN_RSA2048:
//var rsa_key crypto.RSA2048PublicKey
@ -217,30 +194,59 @@ func (key_certificate KeyCertificate) ConstructSigningPublicKey(data []byte) (si
return
}
//
// Return the size of a Signature corresponding to the Key Certificate's
// SigningPublicKey type.
//
// SignatureSize return the size of a Signature corresponding to the Key Certificate's SigningPublicKey type.
func (key_certificate KeyCertificate) SignatureSize() (size int) {
sizes := map[int]int{
KEYCERT_SIGN_DSA_SHA1: 40,
KEYCERT_SIGN_P256: 64,
KEYCERT_SIGN_P384: 96,
KEYCERT_SIGN_P521: 132,
KEYCERT_SIGN_RSA2048: 256,
KEYCERT_SIGN_RSA3072: 384,
KEYCERT_SIGN_RSA4096: 512,
KEYCERT_SIGN_ED25519: 64,
KEYCERT_SIGN_ED25519PH: 64,
}
key_type, err := key_certificate.SigningPublicKeyType()
if err != nil {
log.WithFields(log.Fields{
"at": "(KeyCertificate) SignatureSize",
"key_type": key_type,
"reason": "failed to read signing public key type",
}).Error("error getting signature size")
return 0
KEYCERT_SIGN_DSA_SHA1: KEYCERT_SIGN_DSA_SHA1_SIZE,
KEYCERT_SIGN_P256: KEYCERT_SIGN_P256_SIZE,
KEYCERT_SIGN_P384: KEYCERT_SIGN_P384_SIZE,
KEYCERT_SIGN_P521: KEYCERT_SIGN_P521_SIZE,
KEYCERT_SIGN_RSA2048: KEYCERT_SIGN_RSA2048_SIZE,
KEYCERT_SIGN_RSA3072: KEYCERT_SIGN_RSA3072_SIZE,
KEYCERT_SIGN_RSA4096: KEYCERT_SIGN_RSA4096_SIZE,
KEYCERT_SIGN_ED25519: KEYCERT_SIGN_ED25519_SIZE,
KEYCERT_SIGN_ED25519PH: KEYCERT_SIGN_ED25519PH_SIZE,
}
key_type := key_certificate.SigningPublicKeyType()
return sizes[int(key_type)]
}
// CryptoSize return the size of a Public Key corresponding to the Key Certificate's PublicKey type.
func (key_certificate KeyCertificate) CryptoSize() (size int) {
sizes := map[int]int{
KEYCERT_CRYPTO_ELG: KEYCERT_CRYPTO_ELG_SIZE,
KEYCERT_CRYPTO_P256: KEYCERT_CRYPTO_P256_SIZE,
KEYCERT_CRYPTO_P384: KEYCERT_CRYPTO_P384_SIZE,
KEYCERT_CRYPTO_P521: KEYCERT_CRYPTO_P521_SIZE,
KEYCERT_CRYPTO_X25519: KEYCERT_CRYPTO_X25519_SIZE,
}
key_type := key_certificate.PublicKeyType()
return sizes[int(key_type)]
}
// NewKeyCertificate creates a new *KeyCertificate from []byte using ReadCertificate.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func NewKeyCertificate(bytes []byte) (key_certificate *KeyCertificate, remainder []byte, err error) {
var certificate *Certificate
certificate, remainder, err = ReadCertificate(bytes)
if err != nil {
return
}
if len(bytes) < KEYCERT_MIN_SIZE {
err = errors.New("error parsing key certificate: not enough data")
}
key_certificate = &KeyCertificate{
Certificate: certificate,
spkType: Integer(bytes[4:5]),
cpkType: Integer(bytes[6:7]),
}
remainder = bytes[KEYCERT_MIN_SIZE:]
return
}
// KeyCertificateFromCertificate returns a *KeyCertificate from a *Certificate.
func KeyCertificateFromCertificate(certificate *Certificate) *KeyCertificate {
k, _, _ := NewKeyCertificate(certificate.RawBytes())
return k
}

View File

@ -1,15 +1,16 @@
package common
package key_certificate
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSingingPublicKeyTypeReturnsCorrectInteger(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00})
pk_type, err := key_cert.SigningPublicKeyType()
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00})
pk_type := key_cert.SigningPublicKeyType()
assert.Nil(err, "SigningPublicKeyType() returned error with valid data")
assert.Equal(pk_type, KEYCERT_SIGN_P521, "SigningPublicKeyType() did not return correct typec")
@ -18,8 +19,10 @@ func TestSingingPublicKeyTypeReturnsCorrectInteger(t *testing.T) {
func TestSingingPublicKeyTypeReportsWhenDataTooSmall(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x01, 0x00})
_, err := key_cert.SigningPublicKeyType()
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x01, 0x00})
sk_type := key_cert.SigningPublicKeyType()
assert.Equal(sk_type, 0, "SigningPublicKeyType() did not return correct typec")
if assert.NotNil(err) {
assert.Equal("error parsing key certificate: not enough data", err.Error(), "correct error message should be returned")
@ -29,8 +32,8 @@ func TestSingingPublicKeyTypeReportsWhenDataTooSmall(t *testing.T) {
func TestPublicKeyTypeReturnsCorrectInteger(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03})
pk_type, err := key_cert.PublicKeyType()
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03})
pk_type := key_cert.PublicKeyType()
assert.Nil(err, "PublicKey() returned error with valid data")
assert.Equal(pk_type, KEYCERT_SIGN_P521, "PublicKeyType() did not return correct typec")
@ -39,20 +42,21 @@ func TestPublicKeyTypeReturnsCorrectInteger(t *testing.T) {
func TestPublicKeyTypeReportsWhenDataTooSmall(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x02, 0x00, 0x00})
_, err := key_cert.PublicKeyType()
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x02, 0x00, 0x00})
pk_type := key_cert.PublicKeyType()
if assert.NotNil(err) {
assert.Equal("error parsing key certificate: not enough data", err.Error(), "correct error message should be returned")
}
assert.Equal(pk_type, 0, "PublicKeyType() did not return correct typec")
}
func TestConstructPublicKeyReportsWhenDataTooSmall(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
data := make([]byte, 255)
_, err := key_cert.ConstructPublicKey(data)
_, err = key_cert.ConstructPublicKey(data)
if assert.NotNil(err) {
assert.Equal("error constructing public key: not enough data", err.Error(), "correct error message should be returned")
@ -62,7 +66,7 @@ func TestConstructPublicKeyReportsWhenDataTooSmall(t *testing.T) {
func TestConstructPublicKeyReturnsCorrectDataWithElg(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
data := make([]byte, 256)
pk, err := key_cert.ConstructPublicKey(data)
@ -73,9 +77,9 @@ func TestConstructPublicKeyReturnsCorrectDataWithElg(t *testing.T) {
func TestConstructSigningPublicKeyReportsWhenDataTooSmall(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
data := make([]byte, 127)
_, err := key_cert.ConstructSigningPublicKey(data)
_, err = key_cert.ConstructSigningPublicKey(data)
if assert.NotNil(err) {
assert.Equal("error constructing signing public key: not enough data", err.Error(), "correct error message should be returned")
@ -85,7 +89,7 @@ func TestConstructSigningPublicKeyReportsWhenDataTooSmall(t *testing.T) {
func TestConstructSigningPublicKeyWithDSASHA1(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
data := make([]byte, 128)
spk, err := key_cert.ConstructSigningPublicKey(data)
@ -96,7 +100,7 @@ func TestConstructSigningPublicKeyWithDSASHA1(t *testing.T) {
func TestConstructSigningPublicKeyWithP256(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01})
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01})
data := make([]byte, 128)
spk, err := key_cert.ConstructSigningPublicKey(data)
@ -107,7 +111,7 @@ func TestConstructSigningPublicKeyWithP256(t *testing.T) {
func TestConstructSigningPublicKeyWithP384(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02})
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02})
data := make([]byte, 128)
spk, err := key_cert.ConstructSigningPublicKey(data)
@ -118,7 +122,7 @@ func TestConstructSigningPublicKeyWithP384(t *testing.T) {
func TestConstructSigningPublicKeyWithP521(t *testing.T) {
assert := assert.New(t)
key_cert := KeyCertificate([]byte{0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00})
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00})
data := make([]byte, 128)
spk, err := key_cert.ConstructSigningPublicKey(data)

View File

@ -1,9 +1,32 @@
package common
// Package keys_and_cert implements the I2P KeysAndCert common data structure
package keys_and_cert
import (
"errors"
. "github.com/go-i2p/go-i2p/lib/common/certificate"
. "github.com/go-i2p/go-i2p/lib/common/key_certificate"
"github.com/go-i2p/go-i2p/lib/crypto"
log "github.com/sirupsen/logrus"
)
// Sizes of various KeysAndCert structures and requirements
const (
KEYS_AND_CERT_PUBKEY_SIZE = 256
KEYS_AND_CERT_SPK_SIZE = 128
KEYS_AND_CERT_MIN_SIZE = 387
KEYS_AND_CERT_DATA_SIZE = 384
)
/*
I2P KeysAndCert
https://geti2p.net/spec/common-structures#keysandcert
Accurate for version 0.9.24
[KeysAndCert]
Accurate for version 0.9.49
Description
An encryption public key, a signing public key, and a certificate, used as either a RouterIdentity or a Destination.
Contents
A PublicKey followed by a SigningPublicKey and then a Certificate.
+----+----+----+----+----+----+----+----+
| public_key |
@ -33,11 +56,11 @@ public_key :: PublicKey (partial or full)
padding :: random data
length -> 0 bytes or as specified in key certificate
padding length + signing_key length == KEYS_AND_CERT_SPK_SIZE bytes
padding length + signing_key length == 128 bytes
signing__key :: SigningPublicKey (partial or full)
length -> 128 bytes or as specified in key certificate
padding length + signing_key length == KEYS_AND_CERT_SPK_SIZE bytes
padding length + signing_key length == 128 bytes
certificate :: Certificate
length -> >= 3 bytes
@ -45,32 +68,25 @@ certificate :: Certificate
total length: 387+ bytes
*/
import (
"errors"
"github.com/go-i2p/go-i2p/lib/crypto"
log "github.com/sirupsen/logrus"
)
// Sizes of various KeysAndCert structures and requirements
const (
KEYS_AND_CERT_PUBKEY_SIZE = 256
KEYS_AND_CERT_SPK_SIZE = 128
KEYS_AND_CERT_MIN_SIZE = 387
KEYS_AND_CERT_DATA_SIZE = 384
)
type KeysAndCert []byte
// KeysAndCert is the represenation of an I2P KeysAndCert.
//
// Return the PublicKey for this KeysAndCert, reading from the Key Certificate if it is present to
// determine correct lengths.
//
func (keys_and_cert KeysAndCert) PublicKey() (key crypto.PublicKey, err error) {
cert, err := keys_and_cert.Certificate()
if err != nil {
return
}
cert_len, err := cert.Length()
// https://geti2p.net/spec/common-structures#keysandcert
type KeysAndCert struct {
KeyCertificate *KeyCertificate
publicKey crypto.PublicKey
padding []byte
signingPublicKey crypto.SigningPublicKey
}
// Bytes returns the entire KeyCertificate in []byte form, trims payload to specified length.
func (keys_and_cert *KeysAndCert) Bytes() []byte {
return keys_and_cert.KeyCertificate.Bytes()
}
// PublicKey returns the public key as a crypto.PublicKey.
func (keys_and_cert *KeysAndCert) PublicKey() (key crypto.PublicKey) {
/*cert := keys_and_cert.Certificate()
cert_len := cert.Length()
if err != nil {
return
}
@ -82,12 +98,12 @@ func (keys_and_cert KeysAndCert) PublicKey() (key crypto.PublicKey, err error) {
key = elg_key
} else {
// A Certificate is present in this KeysAndCert
cert_type, _ := cert.Type()
cert_type := cert.Type()
if cert_type == CERT_KEY {
// This KeysAndCert contains a Key Certificate, construct
// a PublicKey from the data in the KeysAndCert and
// any additional data in the Certificate.
key, err = KeyCertificate(cert).ConstructPublicKey(
key, err = KeyCertificateFromCertificate(cert).ConstructPublicKey(
keys_and_cert[:KEYS_AND_CERT_PUBKEY_SIZE],
)
} else {
@ -104,19 +120,14 @@ func (keys_and_cert KeysAndCert) PublicKey() (key crypto.PublicKey, err error) {
}
}
return
return*/
return keys_and_cert.publicKey
}
//
// Return the SigningPublicKey for this KeysAndCert, reading from the Key Certificate if it is present to
// determine correct lengths.
//
func (keys_and_cert KeysAndCert) SigningPublicKey() (signing_public_key crypto.SigningPublicKey, err error) {
cert, err := keys_and_cert.Certificate()
if err != nil {
return
}
cert_len, err := cert.Length()
// SigningPublicKey returns the signing public key.
func (keys_and_cert *KeysAndCert) SigningPublicKey() (signing_public_key crypto.SigningPublicKey) {
/*cert := keys_and_cert.Certificate()
cert_len := cert.Length()
if err != nil {
return
}
@ -128,12 +139,12 @@ func (keys_and_cert KeysAndCert) SigningPublicKey() (signing_public_key crypto.S
signing_public_key = dsa_pk
} else {
// A Certificate is present in this KeysAndCert
cert_type, _ := cert.Type()
cert_type := cert.Type()
if cert_type == CERT_KEY {
// This KeysAndCert contains a Key Certificate, construct
// a SigningPublicKey from the data in the KeysAndCert and
// any additional data in the Certificate.
signing_public_key, err = KeyCertificate(cert).ConstructSigningPublicKey(
signing_public_key, err = KeyCertificateFromCertificate(cert).ConstructSigningPublicKey(
keys_and_cert[KEYS_AND_CERT_PUBKEY_SIZE : KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_SIZE],
)
} else {
@ -145,37 +156,31 @@ func (keys_and_cert KeysAndCert) SigningPublicKey() (signing_public_key crypto.S
signing_public_key = dsa_pk
}
}
return
}*/
return keys_and_cert.signingPublicKey
}
//
// Return the Certificate contained in the KeysAndCert and any errors encountered while parsing the
// KeysAndCert or Certificate.
//
func (keys_and_cert KeysAndCert) Certificate() (cert Certificate, err error) {
keys_cert_len := len(keys_and_cert)
if keys_cert_len < KEYS_AND_CERT_MIN_SIZE {
// Certfificate returns the certificate.
func (keys_and_cert *KeysAndCert) Certificate() (cert *Certificate) {
return keys_and_cert.KeyCertificate.Certificate
}
// NewKeysAndCert creates a new *KeysAndCert from []byte using ReadKeysAndCert.
// Returns a pointer to KeysAndCert unlike ReadKeysAndCert.
func NewKeysAndCert(data []byte) (keys_and_cert *KeysAndCert, remainder []byte, err error) {
data_len := len(data)
keys_and_cert = &KeysAndCert{}
if data_len < KEYS_AND_CERT_MIN_SIZE && data_len > KEYS_AND_CERT_DATA_SIZE {
log.WithFields(log.Fields{
"at": "(KeysAndCert) Certificate",
"data_len": keys_cert_len,
"at": "ReadKeysAndCert",
"data_len": data_len,
"required_len": KEYS_AND_CERT_MIN_SIZE,
"reason": "not enough data",
}).Error("error parsing keys and cert")
err = errors.New("error parsing KeysAndCert: data is smaller than minimum valid size")
keys_and_cert.KeyCertificate, remainder, _ = NewKeyCertificate(data[KEYS_AND_CERT_DATA_SIZE:])
return
}
cert, _, err = ReadCertificate(keys_and_cert[KEYS_AND_CERT_DATA_SIZE:])
return
}
//
// Read a KeysAndCert from a slice of bytes, retuning it and the remaining data as well as any errors
// encoutered parsing the KeysAndCert.
//
func ReadKeysAndCert(data []byte) (keys_and_cert KeysAndCert, remainder []byte, err error) {
data_len := len(data)
if data_len < KEYS_AND_CERT_MIN_SIZE {
} else if data_len < KEYS_AND_CERT_DATA_SIZE {
log.WithFields(log.Fields{
"at": "ReadKeysAndCert",
"data_len": data_len,
@ -185,19 +190,22 @@ func ReadKeysAndCert(data []byte) (keys_and_cert KeysAndCert, remainder []byte,
err = errors.New("error parsing KeysAndCert: data is smaller than minimum valid size")
return
}
keys_and_cert = KeysAndCert(data[:KEYS_AND_CERT_MIN_SIZE])
cert, _ := keys_and_cert.Certificate()
cert_len, cert_len_err := cert.Length()
if cert_len == 0 {
remainder = data[KEYS_AND_CERT_MIN_SIZE:]
return
keys_and_cert.KeyCertificate, remainder, err = NewKeyCertificate(data[KEYS_AND_CERT_DATA_SIZE:])
if err != nil {
return nil, nil, err
}
if data_len < KEYS_AND_CERT_MIN_SIZE+cert_len {
keys_and_cert = append(keys_and_cert, data[KEYS_AND_CERT_MIN_SIZE:]...)
err = cert_len_err
} else {
keys_and_cert = append(keys_and_cert, data[KEYS_AND_CERT_MIN_SIZE:KEYS_AND_CERT_MIN_SIZE+cert_len]...)
remainder = data[KEYS_AND_CERT_MIN_SIZE+cert_len:]
// TODO: this only supports one key type right now and it's the old key type, but the layout is the same.
// a case-switch which sets the size of the SPK and the PK should be used to replace the referenced KEYS_AND_CERT_PUBKEY_SIZE
// and KEYS_AND_CERT_SPK_SIZE constants in the future.
keys_and_cert.publicKey, err = keys_and_cert.KeyCertificate.ConstructPublicKey(data[:keys_and_cert.KeyCertificate.CryptoSize()])
if err != nil {
return nil, nil, err
}
return
keys_and_cert.signingPublicKey, err = keys_and_cert.KeyCertificate.ConstructSigningPublicKey(data[KEYS_AND_CERT_DATA_SIZE-keys_and_cert.KeyCertificate.SignatureSize() : KEYS_AND_CERT_DATA_SIZE])
if err != nil {
return nil, nil, err
}
padding := data[KEYS_AND_CERT_PUBKEY_SIZE : KEYS_AND_CERT_DATA_SIZE-KEYS_AND_CERT_SPK_SIZE]
keys_and_cert.padding = padding
return keys_and_cert, remainder, err
}

View File

@ -1,27 +1,22 @@
package common
package keys_and_cert
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCertificateWithMissingData(t *testing.T) {
/*func TestCertificateWithMissingData(t *testing.T) {
assert := assert.New(t)
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01}
data := make([]byte, 128+256)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
cert, err := keys_and_cert.Certificate()
_, _, err := NewKeysAndCert(data)
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
}
cert_bytes := []byte(cert)
if assert.Equal(len(cert_data), len(cert_bytes)) {
assert.Equal(cert_bytes, cert_data, "keys_and_cert.Certificate() did not return available data when cert was missing some data")
}
}
}*/
func TestCertificateWithValidData(t *testing.T) {
assert := assert.New(t)
@ -29,11 +24,12 @@ func TestCertificateWithValidData(t *testing.T) {
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
data := make([]byte, 128+256)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
cert, err := keys_and_cert.Certificate()
keys_and_cert, _, err := NewKeysAndCert(data)
assert.Nil(err)
cert_bytes := []byte(cert)
cert := keys_and_cert.Certificate()
cert_bytes := cert.Bytes()
if assert.Equal(len(cert_data), len(cert_bytes)) {
assert.Equal(cert_bytes, cert_data, "keys_and_cert.Certificate() did not return correct data with valid cert")
}
@ -47,9 +43,9 @@ func TestPublicKeyWithBadData(t *testing.T) {
data := make([]byte, 128)
data = append(data, pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
pub_key, err := keys_and_cert.PublicKey()
pub_key := keys_and_cert.PublicKey()
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
@ -64,9 +60,9 @@ func TestPublicKeyWithBadCertificate(t *testing.T) {
data := make([]byte, 128)
data = append(data, pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
pub_key, err := keys_and_cert.PublicKey()
pub_key := keys_and_cert.PublicKey()
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
}
@ -81,9 +77,9 @@ func TestPublicKeyWithNullCertificate(t *testing.T) {
data := make([]byte, 128)
data = append(data, pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
pub_key, err := keys_and_cert.PublicKey()
pub_key := keys_and_cert.PublicKey()
assert.Nil(err)
assert.Equal(len(pub_key_data), pub_key.Len())
}
@ -96,9 +92,9 @@ func TestPublicKeyWithKeyCertificate(t *testing.T) {
data := make([]byte, 128)
data = append(data, pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
pub_key, err := keys_and_cert.PublicKey()
pub_key := keys_and_cert.PublicKey()
assert.Nil(err)
assert.Equal(len(pub_key_data), pub_key.Len())
}
@ -111,9 +107,9 @@ func TestSigningPublicKeyWithBadData(t *testing.T) {
data := make([]byte, 93)
data = append(data, pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
signing_pub_key, err := keys_and_cert.SigningPublicKey()
signing_pub_key := keys_and_cert.SigningPublicKey()
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
@ -128,9 +124,9 @@ func TestSigningPublicKeyWithBadCertificate(t *testing.T) {
data := make([]byte, 128)
data = append(data, pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
signing_pub_key, err := keys_and_cert.SigningPublicKey()
signing_pub_key := keys_and_cert.SigningPublicKey()
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
}
@ -145,9 +141,9 @@ func TestSigningPublicKeyWithNullCertificate(t *testing.T) {
signing_pub_key_data := make([]byte, 128)
data := append(pub_key_data, signing_pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
signing_pub_key, err := keys_and_cert.SigningPublicKey()
signing_pub_key := keys_and_cert.SigningPublicKey()
assert.Nil(err)
assert.Equal(len(signing_pub_key_data), signing_pub_key.Len())
}
@ -160,130 +156,77 @@ func TestSigningPublicKeyWithKeyCertificate(t *testing.T) {
signing_pub_key_data := make([]byte, 128)
data := append(pub_key_data, signing_pub_key_data...)
data = append(data, cert_data...)
keys_and_cert := KeysAndCert(data)
keys_and_cert, _, err := NewKeysAndCert(data)
signing_pub_key, err := keys_and_cert.SigningPublicKey()
signing_pub_key := keys_and_cert.SigningPublicKey()
assert.Nil(err)
assert.Equal(len(signing_pub_key_data), signing_pub_key.Len())
}
func TestReadKeysAndCertWithMissingData(t *testing.T) {
func TestNewKeysAndCertWithMissingData(t *testing.T) {
assert := assert.New(t)
cert_data := make([]byte, 128)
keys_and_cert, remainder, err := ReadKeysAndCert(cert_data)
_, remainder, err := NewKeysAndCert(cert_data)
assert.Equal(0, len(remainder))
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
_, err = keys_and_cert.PublicKey()
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
_, err = keys_and_cert.SigningPublicKey()
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
_, err = keys_and_cert.Certificate()
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
}
func TestReadKeysAndCertWithMissingCertData(t *testing.T) {
func TestNewKeysAndCertWithMissingCertData(t *testing.T) {
assert := assert.New(t)
cert_data := make([]byte, 128+256)
cert_data = append(cert_data, []byte{0x05, 0x00, 0x04, 0x00, 0x01}...)
keys_and_cert, remainder, err := ReadKeysAndCert(cert_data)
_, remainder, err := NewKeysAndCert(cert_data)
assert.Equal(0, len(remainder))
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
}
_, err = keys_and_cert.PublicKey()
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
}
_, err = keys_and_cert.SigningPublicKey()
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
}
_, err = keys_and_cert.Certificate()
if assert.NotNil(err) {
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
}
}
func TestReadKeysAndCertWithValidDataWithCertificate(t *testing.T) {
func TestNewKeysAndCertWithValidDataWithCertificate(t *testing.T) {
assert := assert.New(t)
cert_data := make([]byte, 128+256)
cert_data = append(cert_data, []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}...)
keys_and_cert, remainder, err := ReadKeysAndCert(cert_data)
_, remainder, err := NewKeysAndCert(cert_data)
assert.Equal(0, len(remainder))
assert.Nil(err)
_, err = keys_and_cert.PublicKey()
assert.Nil(err, "keys_and_cert.PublicKey() returned error with valid data containing certificate")
_, err = keys_and_cert.SigningPublicKey()
assert.Nil(err, "keys_and_cert.SigningPublicKey() returned error with valid data containing certificate")
_, err = keys_and_cert.Certificate()
assert.Nil(err, "keys_and_cert.Certificate() returned error with valid data containing certificate")
}
func TestReadKeysAndCertWithValidDataWithoutCertificate(t *testing.T) {
func TestNewKeysAndCertWithValidDataWithoutCertificate(t *testing.T) {
assert := assert.New(t)
cert_data := make([]byte, 128+256)
cert_data = append(cert_data, []byte{0x00, 0x00, 0x00}...)
keys_and_cert, remainder, err := ReadKeysAndCert(cert_data)
_, remainder, err := NewKeysAndCert(cert_data)
assert.Equal(0, len(remainder))
assert.Nil(err)
_, err = keys_and_cert.PublicKey()
assert.Nil(err, "keys_and_cert.PublicKey() returned error with valid data not containing certificate")
_, err = keys_and_cert.SigningPublicKey()
assert.Nil(err, "keys_and_cert.SigningPublicKey() returned error with valid data not containing certificate")
_, err = keys_and_cert.Certificate()
assert.Nil(err, "keys_and_cert.Certificate() returned error with valid data not containing certificate")
}
func TestReadKeysAndCertWithValidDataWithCertificateAndRemainder(t *testing.T) {
func TestNewKeysAndCertWithValidDataWithCertificateAndRemainder(t *testing.T) {
assert := assert.New(t)
cert_data := make([]byte, 128+256)
cert_data = append(cert_data, []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x41}...)
keys_and_cert, remainder, err := ReadKeysAndCert(cert_data)
_, remainder, err := NewKeysAndCert(cert_data)
if assert.Equal(1, len(remainder)) {
assert.Equal("A", string(remainder[0]))
}
assert.Nil(err)
_, err = keys_and_cert.PublicKey()
assert.Nil(err, "keys_and_cert.PublicKey() returned error with valid data containing certificate")
_, err = keys_and_cert.SigningPublicKey()
assert.Nil(err, "keys_and_cert.SigningPublicKey() returned error with valid data containing certificate")
_, err = keys_and_cert.Certificate()
assert.Nil(err, "keys_and_cert.Certificate() returned error with valid data containing certificate")
}
func TestReadKeysAndCertWithValidDataWithoutCertificateAndRemainder(t *testing.T) {
func TestNewKeysAndCertWithValidDataWithoutCertificateAndRemainder(t *testing.T) {
assert := assert.New(t)
cert_data := make([]byte, 128+256)
cert_data = append(cert_data, []byte{0x00, 0x00, 0x00, 0x41}...)
keys_and_cert, remainder, err := ReadKeysAndCert(cert_data)
_, remainder, err := NewKeysAndCert(cert_data)
if assert.Equal(1, len(remainder)) {
assert.Equal("A", string(remainder[0]))
}
assert.Nil(err)
_, err = keys_and_cert.PublicKey()
assert.Nil(err, "keys_and_cert.PublicKey() returned error with valid data not containing certificate")
_, err = keys_and_cert.SigningPublicKey()
assert.Nil(err, "keys_and_cert.SigningPublicKey() returned error with valid data not containing certificate")
_, err = keys_and_cert.Certificate()
assert.Nil(err, "keys_and_cert.Certificate() returned error with valid data not containing certificate")
}

View File

@ -1,64 +0,0 @@
package common
/*
I2P Lease
https://geti2p.net/spec/common-structures#lease
Accurate for version 0.9.24
+----+----+----+----+----+----+----+----+
| tunnel_gw |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| tunnel_id | end_date
+----+----+----+----+----+----+----+----+
|
+----+----+----+----+
tunnel_gw :: Hash of the RouterIdentity of the tunnel gateway
length -> 32 bytes
tunnel_id :: TunnelId
length -> 4 bytes
end_date :: Date
length -> 8 bytes
*/
// Sizes or various components of a Lease
const (
LEASE_SIZE = 44
LEASE_HASH_SIZE = 32
LEASE_TUNNEL_ID_SIZE = 4
)
type Lease [LEASE_SIZE]byte
//
// Return the first 32 bytes of the Lease as a Hash.
//
func (lease Lease) TunnelGateway() (hash Hash) {
copy(hash[:], lease[:LEASE_HASH_SIZE])
return
}
//
// Parse the TunnelID Integer in the Lease.
//
func (lease Lease) TunnelID() uint32 {
return uint32(
Integer(lease[LEASE_HASH_SIZE : LEASE_HASH_SIZE+LEASE_TUNNEL_ID_SIZE]),
)
}
//
// Return the Date inside the Lease.
//
func (lease Lease) Date() (date Date) {
copy(date[:], lease[LEASE_HASH_SIZE+LEASE_TUNNEL_ID_SIZE:])
return
}

85
lib/common/lease/lease.go Normal file
View File

@ -0,0 +1,85 @@
// Package lease implements the I2P lease common data structure
package lease
import . "github.com/go-i2p/go-i2p/lib/common/data"
// Sizes in bytes of various components of a Lease
const (
LEASE_SIZE = 44
LEASE_HASH_SIZE = 32
LEASE_TUNNEL_ID_SIZE = 4
)
/*
[Lease]
Accurate for version 0.9.49
Description
Defines the authorization for a particular tunnel to receive messages targeting a Destination.
Contents
SHA256 Hash of the RouterIdentity of the gateway router, then the TunnelId and finally an end Date.
+----+----+----+----+----+----+----+----+
| tunnel_gw |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| tunnel_id | end_date
+----+----+----+----+----+----+----+----+
|
+----+----+----+----+
tunnel_gw :: Hash of the RouterIdentity of the tunnel gateway
length -> 32 bytes
tunnel_id :: TunnelId
length -> 4 bytes
end_date :: Date
length -> 8 bytes
*/
// Lease is the represenation of an I2P Lease.
//
// https://geti2p.net/spec/common-structures#lease
type Lease [LEASE_SIZE]byte
// TunnelGateway returns the tunnel gateway as a Hash.
func (lease Lease) TunnelGateway() (hash Hash) {
copy(hash[:], lease[:LEASE_HASH_SIZE])
return
}
// TunnelID returns the tunnel id as a uint23.
func (lease Lease) TunnelID() uint32 {
i := Integer(lease[LEASE_HASH_SIZE : LEASE_HASH_SIZE+LEASE_TUNNEL_ID_SIZE])
return uint32(
i.Int(),
)
}
// Date returns the date as an I2P Date.
func (lease Lease) Date() (date Date) {
copy(date[:], lease[LEASE_HASH_SIZE+LEASE_TUNNEL_ID_SIZE:])
return
}
// ReadLease returns Lease from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadLease(data []byte) (lease Lease, remainder []byte, err error) {
// TODO: stub
return
}
// NewLease creates a new *NewLease from []byte using ReadLease.
// Returns a pointer to KeysAndCert unlike ReadLease.
func NewLease(data []byte) (lease *Lease, remainder []byte, err error) {
// TODO: stub
return
}

View File

@ -1,9 +1,43 @@
package common
// Package lease_set implements the I2P LeastSet common data structure
package lease_set
import (
"errors"
. "github.com/go-i2p/go-i2p/lib/common/certificate"
. "github.com/go-i2p/go-i2p/lib/common/data"
. "github.com/go-i2p/go-i2p/lib/common/destination"
. "github.com/go-i2p/go-i2p/lib/common/key_certificate"
. "github.com/go-i2p/go-i2p/lib/common/keys_and_cert"
. "github.com/go-i2p/go-i2p/lib/common/lease"
. "github.com/go-i2p/go-i2p/lib/common/signature"
"github.com/go-i2p/go-i2p/lib/crypto"
log "github.com/sirupsen/logrus"
)
// Sizes of various structures in an I2P LeaseSet
const (
LEASE_SET_PUBKEY_SIZE = 256
LEASE_SET_SPK_SIZE = 128
LEASE_SET_SIG_SIZE = 40
)
/*
I2P LeaseSet
https://geti2p.net/spec/common-structures#leaseset
Accurate for version 0.9.24
[LeaseSet]
Accurate for version 0.9.49
Description
Contains all of the currently authorized Leases for a particular Destination, the
PublicKey to which garlic messages can be encrypted, and then the SigningPublicKey
that can be used to revoke this particular version of the structure. The LeaseSet is one
of the two structures stored in the network database (the other being RouterInfo), and
is kered under the SHA256 of the contained Destination.
Contents
Destination, followed by a PublicKey for encryption, then a SigningPublicKey which
can be used to revoke this version of the LeaseSet, then a 1 byte Integer specifying how
many Lease structures are in the set, followed by the actual Lease structures and
finally a Signature of the previous bytes signed by the Destination's SigningPrivateKey.
+----+----+----+----+----+----+----+----+
| destination |
@ -80,35 +114,36 @@ signature :: Signature
length -> 40 bytes or as specified in destination's key certificate
*/
import (
"errors"
"github.com/go-i2p/go-i2p/lib/crypto"
log "github.com/sirupsen/logrus"
)
// Sizes of various structures in an I2P LeaseSet
const (
LEASE_SET_PUBKEY_SIZE = 256
LEASE_SET_SPK_SIZE = 128
LEASE_SET_SIG_SIZE = 40
)
// LeaseSet is the represenation of an I2P LeaseSet.
//
// https://geti2p.net/spec/common-structures#leaseset
type LeaseSet []byte
//
// Read a Destination from the LeaseSet.
//
/*
type LeaseSet struct {
Destination *Destination
EncryptionKey *crypto.ElgPublicKey
SigningKey *crypto.ElgPublicKey
Size *Integer
Leases []*Lease
Signature *Signature
}
*/
// Destination returns the Destination as []byte.
func (lease_set LeaseSet) Destination() (destination Destination, err error) {
keys_and_cert, _, err := ReadKeysAndCert(lease_set)
destination = Destination(keys_and_cert)
keys_and_cert, _, err := NewKeysAndCert(lease_set)
if err != nil {
return
}
destination, _, err = ReadDestination(keys_and_cert.Bytes())
return
}
//
// Return the PublicKey in this LeaseSet and any errors ancountered parsing the LeaseSet.
//
// PublicKey returns the public key as crypto.ElgPublicKey.
// Returns errors encountered during parsing.
func (lease_set LeaseSet) PublicKey() (public_key crypto.ElgPublicKey, err error) {
_, remainder, err := ReadKeysAndCert(lease_set)
_, remainder, err := NewKeysAndCert(lease_set)
remainder_len := len(remainder)
if remainder_len < LEASE_SET_PUBKEY_SIZE {
log.WithFields(log.Fields{
@ -125,21 +160,16 @@ func (lease_set LeaseSet) PublicKey() (public_key crypto.ElgPublicKey, err error
return
}
//
// Return the SigningPublicKey, as specified in the LeaseSet's Destination's Key Certificate if
// present, or a legacy DSA key.
//
// SigningKey returns the signing public key as crypto.SigningPublicKey.
// returns errors encountered during parsing.
func (lease_set LeaseSet) SigningKey() (signing_public_key crypto.SigningPublicKey, err error) {
destination, err := lease_set.Destination()
if err != nil {
return
}
offset := len(destination) + LEASE_SET_PUBKEY_SIZE
cert, err := destination.Certificate()
if err != nil {
return
}
cert_len, err := cert.Length()
offset := len(destination.Bytes()) + LEASE_SET_PUBKEY_SIZE
cert := destination.Certificate()
cert_len := cert.Length()
if err != nil {
return
}
@ -162,12 +192,12 @@ func (lease_set LeaseSet) SigningKey() (signing_public_key crypto.SigningPublicK
signing_public_key = dsa_pk
} else {
// A Certificate is present in this LeaseSet's Destination
cert_type, _ := cert.Type()
cert_type := cert.Type()
if cert_type == CERT_KEY {
// This LeaseSet's Destination's Certificate is a Key Certificate,
// create the signing publickey key using any data that might be
// contained in the key certificate.
signing_public_key, err = KeyCertificate(cert).ConstructSigningPublicKey(
signing_public_key, err = KeyCertificateFromCertificate(cert).ConstructSigningPublicKey(
lease_set[offset : offset+LEASE_SET_SPK_SIZE],
)
} else {
@ -182,11 +212,10 @@ func (lease_set LeaseSet) SigningKey() (signing_public_key crypto.SigningPublicK
return
}
//
// Return the number of Leases specified by the LeaseCount value in this LeaseSet.
//
// LeaseCount returns the numbert of leases specified by the LeaseCount value as int.
// returns errors encountered during parsing.
func (lease_set LeaseSet) LeaseCount() (count int, err error) {
_, remainder, err := ReadKeysAndCert(lease_set)
_, remainder, err := NewKeysAndCert(lease_set)
if err != nil {
return
}
@ -201,7 +230,8 @@ func (lease_set LeaseSet) LeaseCount() (count int, err error) {
err = errors.New("error parsing lease count: not enough data")
return
}
count = Integer([]byte{remainder[LEASE_SET_PUBKEY_SIZE+LEASE_SET_SPK_SIZE]})
c := Integer([]byte{remainder[LEASE_SET_PUBKEY_SIZE+LEASE_SET_SPK_SIZE]})
count = c.Int()
if count > 16 {
log.WithFields(log.Fields{
"at": "(LeaseSet) LeaseCount",
@ -213,15 +243,14 @@ func (lease_set LeaseSet) LeaseCount() (count int, err error) {
return
}
//
// Read the Leases in this LeaseSet, returning a partial set if there is insufficient data.
//
// Leases returns the leases as []Lease.
// returns errors encountered during parsing.
func (lease_set LeaseSet) Leases() (leases []Lease, err error) {
destination, err := lease_set.Destination()
if err != nil {
return
}
offset := len(destination) + LEASE_SET_PUBKEY_SIZE + LEASE_SET_SPK_SIZE + 1
offset := len(destination.Bytes()) + LEASE_SET_PUBKEY_SIZE + LEASE_SET_SPK_SIZE + 1
count, err := lease_set.LeaseCount()
if err != nil {
return
@ -247,10 +276,8 @@ func (lease_set LeaseSet) Leases() (leases []Lease, err error) {
return
}
//
// Return the Signature data for the LeaseSet, as specified in the Destination's
// Key Certificate if present or the 40 bytes following the Leases.
//
// Signature returns the signature as Signature.
// returns errors encountered during parsing.
func (lease_set LeaseSet) Signature() (signature Signature, err error) {
destination, err := lease_set.Destination()
if err != nil {
@ -260,19 +287,16 @@ func (lease_set LeaseSet) Signature() (signature Signature, err error) {
if err != nil {
return
}
start := len(destination) +
start := len(destination.Bytes()) +
LEASE_SET_PUBKEY_SIZE +
LEASE_SET_SPK_SIZE +
1 +
(LEASE_SIZE * lease_count)
cert, err := destination.Certificate()
if err != nil {
return
}
cert_type, _ := cert.Type()
cert := destination.Certificate()
cert_type := cert.Type()
var end int
if cert_type == CERT_KEY {
end = start + KeyCertificate(cert).SignatureSize()
end = start + KeyCertificateFromCertificate(cert).SignatureSize()
} else {
end = start + LEASE_SET_SIG_SIZE
}
@ -291,9 +315,7 @@ func (lease_set LeaseSet) Signature() (signature Signature, err error) {
return
}
//
//
//
// Verify returns nil
func (lease_set LeaseSet) Verify() error {
//data_end := len(destination) +
// LEASE_SET_PUBKEY_SIZE +
@ -311,27 +333,25 @@ func (lease_set LeaseSet) Verify() error {
return nil // verifier.Verify(data, lease_set.Signature())
}
//
// Return the oldest date from all the Leases in the LeaseSet.
//
func (lease_set LeaseSet) NewestExpiration() (oldest Date, err error) {
// NewestExpiration returns the newest lease expiration as an I2P Date.
// Returns errors encountered during parsing.
func (lease_set LeaseSet) NewestExpiration() (newest Date, err error) {
leases, err := lease_set.Leases()
if err != nil {
return
}
oldest = Date{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
newest = Date{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
for _, lease := range leases {
date := lease.Date()
if date.Time().After(oldest.Time()) {
oldest = date
if date.Time().After(newest.Time()) {
newest = date
}
}
return
}
//
// Return the oldest date from all the Leases in the LeaseSet.
//
// OldestExpiration returns the oldest lease expiration as an I2P Date.
// Returns errors encountered during parsing.
func (lease_set LeaseSet) OldestExpiration() (earliest Date, err error) {
leases, err := lease_set.Leases()
if err != nil {

View File

@ -1,15 +1,22 @@
package common
package lease_set
import (
"bytes"
"github.com/stretchr/testify/assert"
"testing"
"github.com/go-i2p/go-i2p/lib/common/certificate"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/lease"
"github.com/go-i2p/go-i2p/lib/common/router_identity"
"github.com/stretchr/testify/assert"
)
func buildDestination() RouterIdentity {
func buildDestination() *router_identity.RouterIdentity {
router_ident_data := make([]byte, 128+256)
router_ident_data = append(router_ident_data, []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}...)
return RouterIdentity(router_ident_data)
ident, _, err := router_identity.NewRouterIdentity(router_ident_data)
panic(err)
return ident
}
func buildPublicKey() []byte {
@ -31,15 +38,15 @@ func buildSigningKey() []byte {
func buildLease(n int) []byte {
data := make([]byte, 0)
for i := 0; i < n; i++ {
lease := make([]byte, LEASE_SIZE)
for p := range lease {
lease[p] = byte(i)
l := make([]byte, lease.LEASE_SIZE)
for p := range l {
l[p] = byte(i)
}
for q := LEASE_SIZE - 9; q < LEASE_SIZE-1; q++ {
lease[q] = 0x00
for q := lease.LEASE_SIZE - 9; q < lease.LEASE_SIZE-1; q++ {
l[q] = 0x00
}
lease[LEASE_SIZE-1] = byte(i + 10)
data = append(data, lease...)
l[lease.LEASE_SIZE-1] = byte(i + 10)
data = append(data, l...)
}
return data
}
@ -54,7 +61,7 @@ func buildSignature(size int) []byte {
func buildFullLeaseSet(n int) LeaseSet {
lease_set_data := make([]byte, 0)
lease_set_data = append(lease_set_data, buildDestination()...)
lease_set_data = append(lease_set_data, buildDestination().KeysAndCert.KeyCertificate.RawBytes()...)
lease_set_data = append(lease_set_data, buildPublicKey()...)
lease_set_data = append(lease_set_data, buildSigningKey()...)
lease_set_data = append(lease_set_data, byte(n))
@ -69,11 +76,11 @@ func TestDestinationIsCorrect(t *testing.T) {
lease_set := buildFullLeaseSet(1)
dest, err := lease_set.Destination()
assert.Nil(err)
dest_cert, err := dest.Certificate()
dest_cert := dest.Certificate()
//assert.Nil(err)
cert_type := dest_cert.Type()
assert.Nil(err)
cert_type, err := dest_cert.Type()
assert.Nil(err)
assert.Equal(CERT_KEY, cert_type)
assert.Equal(certificate.CERT_KEY, cert_type)
}
func TestPublicKeyIsCorrect(t *testing.T) {
@ -142,18 +149,18 @@ func TestLeasesHaveCorrectData(t *testing.T) {
leases, err := lease_set.Leases()
if assert.Nil(err) {
for i := 0; i < count; i++ {
lease := make([]byte, LEASE_SIZE)
for p := range lease {
lease[p] = byte(i)
l := make([]byte, lease.LEASE_SIZE)
for p := range l {
l[p] = byte(i)
}
for q := LEASE_SIZE - 9; q < LEASE_SIZE-1; q++ {
lease[q] = 0x00
for q := lease.LEASE_SIZE - 9; q < lease.LEASE_SIZE-1; q++ {
l[q] = 0x00
}
lease[LEASE_SIZE-1] = byte(i + 10)
l[lease.LEASE_SIZE-1] = byte(i + 10)
assert.Equal(
0,
bytes.Compare(
lease,
l,
leases[i][:],
),
)
@ -184,8 +191,9 @@ func TestNewestExpirationIsCorrect(t *testing.T) {
lease_set := buildFullLeaseSet(5)
latest, err := lease_set.NewestExpiration()
assert.Nil(err)
Date, _, err := common.NewDate([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, byte(4 + 10)})
assert.Equal(
Date{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, byte(4 + 10)},
Date,
latest,
)
}
@ -196,8 +204,9 @@ func TestOldestExpirationIsCorrect(t *testing.T) {
lease_set := buildFullLeaseSet(5)
latest, err := lease_set.OldestExpiration()
assert.Nil(err)
Date, _, err := common.NewDate([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a})
assert.Equal(
Date{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a},
Date,
latest,
)
}

View File

@ -1,228 +0,0 @@
package common
/*
I2P Mapping
https://geti2p.net/spec/common-structures#mapping
Accurate for version 0.9.24
+----+----+----+----+----+----+----+----+
| size |key_string (len + data) | = |
+----+----+----+----+----+----+----+----+
| val_string (len + data) | ; | ...
+----+----+----+----+----+----+----+
size :: Integer
length -> 2 bytes
Total number of bytes that follow
key_string :: String
A string (one byte length followed by UTF-8 encoded characters)
= :: A single byte containing '='
val_string :: String
A string (one byte length followed by UTF-8 encoded characters)
; :: A single byte containing ';'
*/
import (
"encoding/binary"
"errors"
log "github.com/sirupsen/logrus"
"sort"
)
type Mapping []byte
// Parsed key-values pairs inside a Mapping.
type MappingValues [][2]String
//
// Returns the values contained in a Mapping in the form of a MappingValues.
//
func (mapping Mapping) Values() (map_values MappingValues, errs []error) {
var str String
var remainder = mapping
var err error
length := Integer(remainder[:2])
inferred_length := length + 2
remainder = remainder[2:]
mapping_len := len(mapping)
if mapping_len > inferred_length {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"mappnig_bytes_length": mapping_len,
"mapping_length_field": length,
"expected_bytes_length": inferred_length,
"reason": "data longer than expected",
}).Warn("mapping format warning")
errs = append(errs, errors.New("warning parsing mapping: data exists beyond length of mapping"))
} else if inferred_length > mapping_len {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"mappnig_bytes_length": mapping_len,
"mapping_length_field": length,
"expected_bytes_length": inferred_length,
"reason": "data shorter than expected",
}).Warn("mapping format warning")
errs = append(errs, errors.New("warning parsing mapping: mapping length exceeds provided data"))
}
for {
// Read a key, breaking on fatal errors
// and appending warnings
str, remainder, err = ReadString(remainder)
key_str := str
if err != nil {
if stopValueRead(err) {
errs = append(errs, err)
return
}
}
if !beginsWith(remainder, 0x3d) {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"reason": "expected =",
}).Warn("mapping format violation")
errs = append(errs, errors.New("mapping format violation, expected ="))
return
}
remainder = remainder[1:]
// Read a value, breaking on fatal errors
// and appending warnings
str, remainder, err = ReadString(remainder)
val_str := str
if err != nil {
if stopValueRead(err) {
errs = append(errs, err)
return
}
}
if !beginsWith(remainder, 0x3b) {
log.WithFields(log.Fields{
"at": "(Mapping) Values",
"reason": "expected ;",
}).Warn("mapping format violation")
errs = append(errs, errors.New("mapping format violation, expected ;"))
return
}
remainder = remainder[1:]
// Append the key-value pair and break if there is no more data to read
map_values = append(map_values, [2]String{key_str, val_str})
if len(remainder) == 0 {
break
}
}
return
}
//
// Return true if two keys in a mapping are identical.
//
func (mapping Mapping) HasDuplicateKeys() bool {
seen_values := make(map[string]bool)
values, _ := mapping.Values()
for _, pair := range values {
key, _ := pair[0].Data()
if _, present := seen_values[key]; present {
return true
} else {
seen_values[key] = true
}
}
return false
}
//
// Convert a MappingValue struct to a Mapping. The values are first
// sorted in the order defined in mappingOrder.
//
func ValuesToMapping(values MappingValues) (mapping Mapping) {
mappingOrder(values)
for _, kv_pair := range values {
key_string := kv_pair[0]
key_string = append(key_string, []byte("=")[0])
key_value := kv_pair[1]
key_value = append(key_value, []byte(";")[0])
mapping = append(append(mapping, key_string...), key_value...)
}
map_len := len(mapping)
len_bytes := make([]byte, 2)
binary.BigEndian.PutUint16(len_bytes, uint16(map_len))
mapping = append(len_bytes, mapping...)
return
}
//
// Convert a Go map of unformatted strings to a sorted Mapping.
//
func GoMapToMapping(gomap map[string]string) (mapping Mapping, err error) {
map_vals := MappingValues{}
for k, v := range gomap {
key_str, kerr := ToI2PString(k)
if kerr != nil {
err = kerr
return
}
val_str, verr := ToI2PString(v)
if verr != nil {
err = verr
return
}
map_vals = append(
map_vals,
[2]String{key_str, val_str},
)
}
mapping = ValuesToMapping(map_vals)
return
}
type byValue MappingValues
func (set byValue) Len() int { return len(set) }
func (set byValue) Swap(i, j int) { set[i], set[j] = set[j], set[i] }
func (set byValue) Less(i, j int) bool {
data1, _ := set[i][1].Data()
data2, _ := set[j][1].Data()
return data1 < data2
}
type byKey MappingValues
func (set byKey) Len() int { return len(set) }
func (set byKey) Swap(i, j int) { set[i], set[j] = set[j], set[i] }
func (set byKey) Less(i, j int) bool {
data1, _ := set[i][0].Data()
data2, _ := set[j][0].Data()
return data1 < data2
}
//
// I2P Mappings require consistent order for for cryptographic signing, and sorting
// by keys. When new Mappings are created, they are stable sorted first by values
// than by keys to ensure a consistent order.
//
func mappingOrder(values MappingValues) {
sort.Stable(byValue(values))
sort.Stable(byKey(values))
}
//
// Check if the string parsing error indicates that the Mapping
// should no longer be parsed.
//
func stopValueRead(err error) bool {
return err.Error() == "error parsing string: zero length"
}
//
// Determine if the first byte in a slice of bytes is the provided byte.
//
func beginsWith(bytes []byte, chr byte) bool {
return len(bytes) != 0 &&
bytes[0] == chr
}

View File

@ -1,187 +0,0 @@
package common
import (
"bytes"
"errors"
"github.com/stretchr/testify/assert"
"testing"
)
func TestValuesExclusesPairWithBadData(t *testing.T) {
assert := assert.New(t)
bad_key := Mapping([]byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x00})
values, errs := bad_key.Values()
if assert.Equal(len(values), 1, "Values() did not return valid values when some values had bad key") {
key, _ := values[0][0].Data()
val, _ := values[0][1].Data()
assert.Equal(key, "a", "Values() returned by data with invalid key contains incorrect present key")
assert.Equal(val, "b", "Values() returned by data with invalid key contains incorrect present key")
}
assert.Equal(len(errs), 2, "Values() reported wrong error count when some values had invalid data")
}
func TestValuesWarnsMissingData(t *testing.T) {
assert := assert.New(t)
mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62})
_, errs := mapping.Values()
if assert.Equal(len(errs), 2, "Values() reported wrong error count when mapping had missing data") {
assert.Equal(errs[0].Error(), "warning parsing mapping: mapping length exceeds provided data", "correct error message should be returned")
}
}
func TestValuesWarnsExtraData(t *testing.T) {
assert := assert.New(t)
mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x00})
_, errs := mapping.Values()
if assert.Equal(len(errs), 2, "Values() reported wrong error count when mapping had extra data") {
assert.Equal(errs[0].Error(), "warning parsing mapping: data exists beyond length of mapping", "correct error message should be returned")
}
}
func TestValuesEnforcesEqualDelimitor(t *testing.T) {
assert := assert.New(t)
mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x30, 0x01, 0x62, 0x3b})
values, errs := mapping.Values()
if assert.Equal(len(errs), 1, "Values() reported wrong error count when mapping had = format error") {
assert.Equal(errs[0].Error(), "mapping format violation, expected =", "correct error message should be returned")
}
assert.Equal(len(values), 0, "Values() not empty with invalid data due to = format error")
}
func TestValuesEnforcedSemicolonDelimitor(t *testing.T) {
assert := assert.New(t)
mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x30})
values, errs := mapping.Values()
if assert.Equal(len(errs), 1, "Values() reported wrong error count when mapping had ; format error") {
assert.Equal(errs[0].Error(), "mapping format violation, expected ;", "correct error message should be returned")
}
assert.Equal(len(values), 0, "Values() not empty with invalid data due to ; format error")
}
func TestValuesReturnsValues(t *testing.T) {
assert := assert.New(t)
mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b})
values, errs := mapping.Values()
key, kerr := values[0][0].Data()
val, verr := values[0][1].Data()
assert.Nil(errs, "Values() returned a errors with parsing valid data")
assert.Nil(kerr)
assert.Nil(verr)
assert.Equal(key, "a", "Values() did not return key in valid data")
assert.Equal(val, "b", "Values() did not return value in valid data")
}
func TestHasDuplicateKeysTrueWhenDuplicates(t *testing.T) {
assert := assert.New(t)
dups := Mapping([]byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b})
assert.Equal(dups.HasDuplicateKeys(), true, "HasDuplicateKeys() did not report true when duplicate keys present")
}
func TestHasDuplicateKeysFalseWithoutDuplicates(t *testing.T) {
assert := assert.New(t)
mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b})
assert.Equal(mapping.HasDuplicateKeys(), false, "HasDuplicateKeys() did not report false when no duplicate keys present")
}
func TestGoMapToMappingProducesCorrectMapping(t *testing.T) {
assert := assert.New(t)
gomap := map[string]string{"a": "b"}
mapping, err := GoMapToMapping(gomap)
assert.Nil(err, "GoMapToMapping() returned error with valid data")
expected := []byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b}
if bytes.Compare(mapping, expected) != 0 {
t.Fatal("GoMapToMapping did not produce correct Mapping", mapping, expected)
}
}
func TestMappingOrderSortsValuesThenKeys(t *testing.T) {
a, _ := ToI2PString("a")
b, _ := ToI2PString("b")
values := MappingValues{
[2]String{b, b},
[2]String{b, a},
[2]String{a, b},
[2]String{a, a},
}
mappingOrder(values)
for i, pair := range values {
key, _ := pair[0].Data()
value, _ := pair[1].Data()
switch i {
case 0:
if !(key == "a" && value == "a") {
t.Fatal("mappingOrder produced incorrect sort output at", i)
}
case 1:
if !(key == "a" && value == "b") {
t.Fatal("mappingOrder produced incorrect sort output at", i)
}
case 2:
if !(key == "b" && value == "a") {
t.Fatal("mappingOrder produced incorrect sort output at", i)
}
case 3:
if !(key == "b" && value == "b") {
t.Fatal("mappingOrder produced incorrect sort output at", i)
}
}
}
}
func TestStopValueReadTrueWhenCorrectErr(t *testing.T) {
assert := assert.New(t)
status := stopValueRead(errors.New("error parsing string: zero length"))
assert.Equal(status, true, "stopValueRead() did not return true when String error found")
}
func TestStopValueReadFalseWhenWrongErr(t *testing.T) {
assert := assert.New(t)
status := stopValueRead(errors.New("something else"))
assert.Equal(status, false, "stopValueRead() did not return false when non String error found")
}
func TestBeginsWithCorrectWhenTrue(t *testing.T) {
assert := assert.New(t)
slice := []byte{0x41}
assert.Equal(beginsWith(slice, 0x41), true, "beginsWith() did not return true when correct")
}
func TestBeginsWithCorrectWhenFalse(t *testing.T) {
assert := assert.New(t)
slice := []byte{0x00}
assert.Equal(beginsWith(slice, 0x41), false, "beginsWith() did not false when incorrect")
}
func TestBeginsWithCorrectWhenNil(t *testing.T) {
assert := assert.New(t)
slice := make([]byte, 0)
assert.Equal(beginsWith(slice, 0x41), false, "beginsWith() did not return false on empty slice")
}

View File

@ -1,161 +0,0 @@
package common
/*
I2P RouterAddress
https://geti2p.net/spec/common-structures#routeraddress
Accurate for version 0.9.24
+----+----+----+----+----+----+----+----+
|cost| expiration
+----+----+----+----+----+----+----+----+
| transport_style |
+----+----+----+----+-//-+----+----+----+
| |
+ +
| options |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
cost :: Integer
length -> 1 byte
case 0 -> free
case 255 -> expensive
expiration :: Date (must be all zeros, see notes below)
length -> 8 bytes
case null -> never expires
transport_style :: String
length -> 1-256 bytes
options :: Mapping
*/
import (
"errors"
log "github.com/sirupsen/logrus"
)
// Minimum number of bytes in a valid RouterAddress
const (
ROUTER_ADDRESS_MIN_SIZE = 9
)
type RouterAddress []byte
//
// Return the cost integer for this RouterAddress and any errors encountered
// parsing the RouterAddress.
//
func (router_address RouterAddress) Cost() (cost int, err error) {
err, exit := router_address.checkValid()
if exit {
return
}
cost = Integer([]byte{router_address[0]})
return
}
//
// Return the Date this RouterAddress expires and any errors encountered
// parsing the RouterAddress.
//
func (router_address RouterAddress) Expiration() (date Date, err error) {
err, exit := router_address.checkValid()
if exit {
return
}
copy(date[:], router_address[1:ROUTER_ADDRESS_MIN_SIZE])
return
}
//
// Return the Transport type for this RouterAddress and any errors encountered
// parsing the RouterAddress.
//
func (router_address RouterAddress) TransportStyle() (str String, err error) {
err, exit := router_address.checkValid()
if exit {
return
}
str, _, err = ReadString(router_address[ROUTER_ADDRESS_MIN_SIZE:])
return
}
//
// Return the Mapping containing the options for this RouterAddress and any
// errors encountered parsing the RouterAddress.
//
func (router_address RouterAddress) Options() (mapping Mapping, err error) {
err, exit := router_address.checkValid()
if exit {
return
}
_, remainder, err := ReadString(router_address[ROUTER_ADDRESS_MIN_SIZE:])
if len(remainder) == 0 {
return
}
mapping = Mapping(remainder)
return
}
//
// Check if the RouterAddress is empty or if it is too small to contain valid data.
//
func (router_address RouterAddress) checkValid() (err error, exit bool) {
addr_len := len(router_address)
exit = false
if addr_len == 0 {
log.WithFields(log.Fields{
"at": "(RouterAddress) checkValid",
"reason": "no data",
}).Error("invalid router address")
err = errors.New("error parsing RouterAddress: no data")
exit = true
} else if addr_len < ROUTER_ADDRESS_MIN_SIZE {
log.WithFields(log.Fields{
"at": "(RouterAddress) checkValid",
"reason": "data too small (len < ROUTER_ADDRESS_MIN_SIZE)",
}).Warn("router address format warning")
err = errors.New("warning parsing RouterAddress: data too small")
}
return
}
//
// Given a slice of bytes, read a RouterAddress, returning the remaining bytes and any
// errors encountered parsing the RouterAddress.
//
func ReadRouterAddress(data []byte) (router_address RouterAddress, remainder []byte, err error) {
test_address := RouterAddress(data)
err, _ = test_address.checkValid()
if err != nil {
return
}
router_address = append(router_address, data[:ROUTER_ADDRESS_MIN_SIZE]...)
str, remainder, err := ReadString(data[ROUTER_ADDRESS_MIN_SIZE:])
if err != nil {
return
}
router_address = append(router_address, str...)
map_size := 0
mapping := make([]byte, 0)
if len(remainder) >= 2 {
map_size = Integer(remainder[:2])
if len(remainder) < map_size+2 {
err = errors.New("not enough data for map inside router address")
router_address = RouterAddress([]byte{})
remainder = []byte{}
return
}
mapping = remainder[:map_size+2]
router_address = append(router_address, mapping...)
}
remainder = data[ROUTER_ADDRESS_MIN_SIZE+len(str)+len(mapping):]
return
}

View File

@ -0,0 +1,162 @@
// Package router_address implements the I2P RouterAddress common data structure
package router_address
import (
"errors"
. "github.com/go-i2p/go-i2p/lib/common/data"
log "github.com/sirupsen/logrus"
)
// Minimum number of bytes in a valid RouterAddress
const (
ROUTER_ADDRESS_MIN_SIZE = 9
)
/*
[RouterAddress]
Accurate for version 0.9.49
Description
This structure defines the means to contact a router through a transport protocol.
Contents
1 byte Integer defining the relative cost of using the address, where 0 is free and 255 is
expensive, followed by the expiration Date after which the address should not be used,
of if null, the address never expires. After that comes a String defining the transport
protocol this router address uses. Finally there is a Mapping containing all of the
transport specific options necessary to establish the connection, such as IP address,
port number, email address, URL, etc.
+----+----+----+----+----+----+----+----+
|cost| expiration
+----+----+----+----+----+----+----+----+
| transport_style |
+----+----+----+----+-//-+----+----+----+
| |
+ +
| options |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
cost :: Integer
length -> 1 byte
case 0 -> free
case 255 -> expensive
expiration :: Date (must be all zeros, see notes below)
length -> 8 bytes
case null -> never expires
transport_style :: String
length -> 1-256 bytes
options :: Mapping
*/
// RouterAddress is the represenation of an I2P RouterAddress.
//
// https://geti2p.net/spec/common-structures#routeraddress
type RouterAddress struct {
cost *Integer
expiration *Date
transport_style *I2PString
options *Mapping
}
// Bytes returns the router address as a []byte.
func (router_address RouterAddress) Bytes() []byte {
bytes := make([]byte, 0)
bytes = append(bytes, router_address.cost.Bytes()...)
bytes = append(bytes, router_address.expiration.Bytes()...)
strData, err := router_address.transport_style.Data()
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("RouterAddress.Bytes: error getting transport_style bytes")
} else {
bytes = append(bytes, strData...)
}
bytes = append(bytes, router_address.options.Data()...)
return bytes
}
// Cost returns the cost for this RouterAddress as a Go integer.
func (router_address RouterAddress) Cost() int {
return router_address.cost.Int()
}
// Expiration returns the expiration for this RouterAddress as an I2P Date.
func (router_address RouterAddress) Expiration() Date {
return *router_address.expiration
}
// TransportStyle returns the transport style for this RouterAddress as an I2PString.
func (router_address RouterAddress) TransportStyle() I2PString {
return *router_address.transport_style
}
// Options returns the options for this RouterAddress as an I2P Mapping.
func (router_address RouterAddress) Options() Mapping {
return *router_address.options
}
// Check if the RouterAddress is empty or if it is too small to contain valid data.
func (router_address RouterAddress) checkValid() (err error, exit bool) {
return
}
// ReadRouterAddress returns RouterAddress from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadRouterAddress(data []byte) (router_address RouterAddress, remainder []byte, err error) {
if len(data) == 0 {
log.WithField("at", "(RouterAddress) ReadRouterAddress").Error("error parsing RouterAddress: no data")
err = errors.New("error parsing RouterAddress: no data")
return
}
router_address.cost, remainder, err = NewInteger(data, 1)
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterAddress) ReadNewRouterAddress",
"reason": "error parsing cost",
}).Warn("error parsing RouterAddress")
}
router_address.expiration, remainder, err = NewDate(remainder)
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterAddress) ReadNewRouterAddress",
"reason": "error parsing expiration",
}).Error("error parsing RouterAddress")
}
router_address.transport_style, remainder, err = NewI2PString(remainder)
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterAddress) ReadNewRouterAddress",
"reason": "error parsing transport_style",
}).Error("error parsing RouterAddress")
}
var errs []error
router_address.options, remainder, errs = NewMapping(remainder)
for _, err := range errs {
log.WithFields(log.Fields{
"at": "(RouterAddress) ReadNewRouterAddress",
"reason": "error parsing options",
"error": err,
}).Error("error parsing RouterAddress")
}
return
}
// NewRouterAddress creates a new *RouterAddress from []byte using ReadRouterAddress.
// Returns a pointer to RouterAddress unlike ReadRouterAddress.
func NewRouterAddress(data []byte) (router_address *RouterAddress, remainder []byte, err error) {
objrouteraddress, remainder, err := ReadRouterAddress(data)
router_address = &objrouteraddress
return
}

View File

@ -1,41 +1,47 @@
package common
package router_address
import (
"bytes"
"github.com/stretchr/testify/assert"
"testing"
. "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/stretchr/testify/assert"
)
func TestCheckValidReportsEmptySlice(t *testing.T) {
assert := assert.New(t)
router_address := RouterAddress([]byte{})
err, exit := router_address.checkValid()
router_address, _, err := ReadRouterAddress([]byte{})
if assert.NotNil(err) {
assert.Equal(err.Error(), "error parsing RouterAddress: no data", "correct error message should be returned")
}
err, exit := router_address.checkValid()
assert.Equal(exit, true, "checkValid did not indicate to stop parsing on empty slice")
}
func TestCheckRouterAddressValidReportsDataMissing(t *testing.T) {
assert := assert.New(t)
router_address := RouterAddress([]byte{0x01})
err, exit := router_address.checkValid()
router_address, _, err := ReadRouterAddress([]byte{0x01})
if assert.NotNil(err) {
assert.Equal(err.Error(), "warning parsing RouterAddress: data too small", "correct error message should be returned")
}
err, exit := router_address.checkValid()
assert.Equal(exit, false, "checkValid indicates to stop parsing when some fields may be present")
}
func TestCheckRouterAddressValidNoErrWithValidData(t *testing.T) {
assert := assert.New(t)
router_address := RouterAddress([]byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
mapping, _ := GoMapToMapping(map[string]string{"host": "127.0.0.1", "port": "4567"})
router_address = append(router_address, mapping...)
router_address, _, _ := ReadRouterAddress([]byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
mapping, err := GoMapToMapping(map[string]string{"host": "127.0.0.1", "port": "4567"})
assert.Nil(err, "GoMapToMapping() returned error with valid data")
router_address.options = mapping
//router_address = append(router_address, mapping...)
err, exit := router_address.checkValid()
assert.Nil(err, "checkValid() reported error with valid data")
@ -45,8 +51,8 @@ func TestCheckRouterAddressValidNoErrWithValidData(t *testing.T) {
func TestRouterAddressCostReturnsFirstByte(t *testing.T) {
assert := assert.New(t)
router_address := RouterAddress([]byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
cost, err := router_address.Cost()
router_address, _, err := ReadRouterAddress([]byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
cost := router_address.Cost()
assert.Nil(err, "Cost() returned error with valid data")
assert.Equal(cost, 6, "Cost() returned wrong cost")
@ -55,8 +61,8 @@ func TestRouterAddressCostReturnsFirstByte(t *testing.T) {
func TestRouterAddressExpirationReturnsCorrectData(t *testing.T) {
assert := assert.New(t)
router_address := RouterAddress([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00})
expiration, err := router_address.Expiration()
router_address, _, err := ReadRouterAddress([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00})
expiration := router_address.Expiration()
assert.Nil(err, "Expiration() returned error with valid data")
if bytes.Compare(expiration[:], []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}) != 0 {
@ -71,7 +77,7 @@ func TestReadRouterAddressReturnsCorrectRemainderWithoutError(t *testing.T) {
str, _ := ToI2PString("foo")
mapping, _ := GoMapToMapping(map[string]string{"host": "127.0.0.1", "port": "4567"})
router_address_bytes = append(router_address_bytes, []byte(str)...)
router_address_bytes = append(router_address_bytes, mapping...)
router_address_bytes = append(router_address_bytes, mapping.Data()...)
router_address_bytes = append(router_address_bytes, []byte{0x01, 0x02, 0x03}...)
router_address, remainder, err := ReadRouterAddress(router_address_bytes)

View File

@ -1,36 +0,0 @@
package common
/*
I2P RouterIdentity
https://geti2p.net/spec/common-structures#routeridentity
Accurate for version 0.9.24
Identical to KeysAndCert
*/
import (
"github.com/go-i2p/go-i2p/lib/crypto"
)
//
// A RouterIdentity is identical to KeysAndCert.
//
type RouterIdentity []byte
func (router_identity RouterIdentity) PublicKey() (crypto.PublicKey, error) {
return KeysAndCert(router_identity).PublicKey()
}
func (router_identity RouterIdentity) SigningPublicKey() (crypto.SigningPublicKey, error) {
return KeysAndCert(router_identity).SigningPublicKey()
}
func (router_identity RouterIdentity) Certificate() (Certificate, error) {
return KeysAndCert(router_identity).Certificate()
}
func ReadRouterIdentity(data []byte) (router_identity RouterIdentity, remainder []byte, err error) {
keys_and_cert, remainder, err := ReadKeysAndCert(data)
router_identity = RouterIdentity(keys_and_cert)
return
}

View File

@ -0,0 +1,43 @@
// Package router_identity implements the I2P RouterIdentity common data structure
package router_identity
import (
. "github.com/go-i2p/go-i2p/lib/common/keys_and_cert"
)
/*
[RouterIdentity]
Accurate for version 0.9.49
Description
Defines the way to uniquely identify a particular router
Contents
Identical to KeysAndCert.
*/
// RouterIdentity is the represenation of an I2P RouterIdentity.
//
// https://geti2p.net/spec/common-structures#routeridentity
type RouterIdentity struct {
*KeysAndCert
}
// ReadRouterIdentity returns RouterIdentity from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadRouterIdentity(data []byte) (router_identity RouterIdentity, remainder []byte, err error) {
keys_and_cert, remainder, err := NewKeysAndCert(data)
router_identity = RouterIdentity{
keys_and_cert,
}
return
}
// NewRouterIdentity creates a new *RouterIdentity from []byte using ReadRouterIdentity.
// Returns a pointer to RouterIdentity unlike ReadRouterIdentity.
func NewRouterIdentity(data []byte) (router_identity *RouterIdentity, remainder []byte, err error) {
objrouter_identity, remainder, err := ReadRouterIdentity(data)
router_identity = &objrouter_identity
return
}

View File

@ -1,268 +0,0 @@
package common
/*
I2P RouterInfo
https://geti2p.net/spec/common-structures#routerinfo
Accurate for version 0.9.24
+----+----+----+----+----+----+----+----+
| router_ident |
+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| published |
+----+----+----+----+----+----+----+----+
|size| RouterAddress 0 |
+----+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| RouterAddress 1 |
+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| RouterAddress ($size-1) |
+ +
| |
~ ~
~ ~
| |
+----+----+----+----+-//-+----+----+----+
|psiz| options |
+----+----+----+----+-//-+----+----+----+
| signature |
+ +
| |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
router_ident :: RouterIdentity
length -> >= 387 bytes
published :: Date
length -> 8 bytes
size :: Integer
length -> 1 byte
The number of RouterAddresses to follow, 0-255
addresses :: [RouterAddress]
length -> varies
peer_size :: Integer
length -> 1 byte
The number of peer Hashes to follow, 0-255, unused, always zero
value -> 0
options :: Mapping
signature :: Signature
length -> 40 bytes
*/
import (
"errors"
log "github.com/sirupsen/logrus"
)
type RouterInfo []byte
//
// Read a RouterIdentity from the RouterInfo, returning the RouterIdentity and any errors
// encountered parsing the RouterIdentity.
//
func (router_info RouterInfo) RouterIdentity() (router_identity RouterIdentity, err error) {
router_identity, _, err = ReadRouterIdentity(router_info)
return
}
//
// Calculate this RouterInfo's Identity Hash (the sha256 of the RouterIdentity)
// returns error if the RouterIdentity is malformed
//
func (router_info RouterInfo) IdentHash() (h Hash, err error) {
var ri RouterIdentity
ri, err = router_info.RouterIdentity()
if err == nil {
h = HashData(ri)
}
return
}
//
// Return the Date the RouterInfo was published and any errors encountered parsing the RouterInfo.
//
func (router_info RouterInfo) Published() (date Date, err error) {
_, remainder, err := ReadRouterIdentity(router_info)
if err != nil {
return
}
remainder_len := len(remainder)
if remainder_len < 8 {
log.WithFields(log.Fields{
"at": "(RouterInfo) Published",
"data_len": remainder_len,
"required_len": 8,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing date: not enough data")
return
}
copy(date[:], remainder[:8])
return
}
//
// Return the Integer representing the number of RouterAddresses that are contained in this RouterInfo.
//
func (router_info RouterInfo) RouterAddressCount() (count int, err error) {
_, remainder, err := ReadRouterIdentity(router_info)
if err != nil {
return
}
remainder_len := len(remainder)
if remainder_len < 9 {
log.WithFields(log.Fields{
"at": "(RouterInfo) RouterAddressCount",
"data_len": remainder_len,
"required_len": 9,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router addresses: not enough data")
return
}
count = Integer([]byte{remainder[8]})
return
}
//
// Read the RouterAddresses inside this RouterInfo and return them in a slice, returning
// a partial list if data is missing.
//
func (router_info RouterInfo) RouterAddresses() (router_addresses []RouterAddress, err error) {
_, remainder, err := ReadRouterIdentity(router_info)
if err != nil {
return
}
remainder_len := len(remainder)
if remainder_len < 9 {
log.WithFields(log.Fields{
"at": "(RouterInfo) RouterAddresses",
"data_len": remainder_len,
"required_len": 9,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router addresses: not enough data")
return
}
remaining := remainder[9:]
var router_address RouterAddress
addr_count, cerr := router_info.RouterAddressCount()
if cerr != nil {
err = cerr
return
}
for i := 0; i < addr_count; i++ {
router_address, remaining, err = ReadRouterAddress(remaining)
if err == nil {
router_addresses = append(router_addresses, router_address)
}
}
return
}
//
// Return the PeerSize value, currently unused and always zero.
//
func (router_info RouterInfo) PeerSize() int {
// Peer size is unused:
// https://geti2p.net/spec/common-structures#routeraddress
return 0
}
//
// Return the Options Mapping inside this RouterInfo.
//
func (router_info RouterInfo) Options() (mapping Mapping) {
head := router_info.optionsLocation()
size := head + router_info.optionsSize()
mapping = Mapping(router_info[head:size])
return
}
//
// Return the signature of this router info
//
func (router_info RouterInfo) Signature() (signature Signature) {
head := router_info.optionsLocation()
size := head + router_info.optionsSize()
ident, _ := router_info.RouterIdentity()
keyCert := KeyCertificate(ident)
sigSize := keyCert.SignatureSize()
signature = Signature(router_info[size : size+sigSize])
return
}
//
// Used during parsing to determine where in the RouterInfo the Mapping data begins.
//
func (router_info RouterInfo) optionsLocation() (location int) {
data, remainder, err := ReadRouterIdentity(router_info)
if err != nil {
return
}
location += len(data)
remainder_len := len(remainder)
if remainder_len < 9 {
log.WithFields(log.Fields{
"at": "(RouterInfo) optionsLocation",
"data_len": remainder_len,
"required_len": 9,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router addresses: not enough data")
return
}
location += 9
remaining := remainder[9:]
var router_address RouterAddress
var router_addresses []RouterAddress
addr_count, cerr := router_info.RouterAddressCount()
if cerr != nil {
err = cerr
return
}
for i := 0; i < addr_count; i++ {
router_address, remaining, err = ReadRouterAddress(remaining)
if err == nil {
location += len(router_address)
router_addresses = append(router_addresses, router_address)
}
}
location += 1
return
}
//
// Used during parsing to determine the size of the options in the RouterInfo.
//
func (router_info RouterInfo) optionsSize() (size int) {
head := router_info.optionsLocation()
size = Integer(router_info[head:head+2]) + 2
return
}

View File

@ -0,0 +1,379 @@
// Package router_info implements the I2P RouterInfo common data structure
package router_info
import (
"errors"
"strconv"
"strings"
. "github.com/go-i2p/go-i2p/lib/common/data"
. "github.com/go-i2p/go-i2p/lib/common/router_address"
. "github.com/go-i2p/go-i2p/lib/common/router_identity"
. "github.com/go-i2p/go-i2p/lib/common/signature"
log "github.com/sirupsen/logrus"
)
const ROUTER_INFO_MIN_SIZE = 439
var MIN_GOOD_VERSION = 59
const MAX_GOOD_VERSION = 99
/*
[RouterInfo]
Accurate for version 0.9.49
Description
Defines all of the data that a router wants to public for the network to see. The
RouterInfo is one of two structures stored in the network database (the other being
LeaseSet), and is keyed under the SHA256 of the contained RouterIdentity.
Contents
RouterIdentity followed by the Date, when the entry was published
+----+----+----+----+----+----+----+----+
| router_ident |
+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| published |
+----+----+----+----+----+----+----+----+
|size| RouterAddress 0 |
+----+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| RouterAddress 1 |
+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| RouterAddress ($size-1) |
+ +
| |
~ ~
~ ~
| |
+----+----+----+----+-//-+----+----+----+
|psiz| options |
+----+----+----+----+-//-+----+----+----+
| signature |
+ +
| |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
router_ident :: RouterIdentity
length -> >= 387 bytes
published :: Date
length -> 8 bytes
size :: Integer
length -> 1 byte
The number of RouterAddresses to follow, 0-255
addresses :: [RouterAddress]
length -> varies
peer_size :: Integer
length -> 1 byte
The number of peer Hashes to follow, 0-255, unused, always zero
value -> 0
options :: Mapping
signature :: Signature
length -> 40 bytes
*/
// RouterInfo is the represenation of an I2P RouterInfo.
//
// https://geti2p.net/spec/common-structures#routerinfo
type RouterInfo struct {
router_identity *RouterIdentity
published *Date
size *Integer
addresses []*RouterAddress
peer_size *Integer
options *Mapping
signature *Signature
}
// Bytes returns the RouterInfo as a []byte suitable for writing to a stream.
func (router_info RouterInfo) Bytes() ([]byte, error) {
var err error
var bytes []byte
bytes = append(bytes, router_info.router_identity.KeysAndCert.Bytes()...)
bytes = append(bytes, router_info.published.Bytes()...)
bytes = append(bytes, router_info.size.Bytes()...)
for _, router_address := range router_info.addresses {
bytes = append(bytes, router_address.Bytes()...)
}
bytes = append(bytes, router_info.peer_size.Bytes()...)
bytes = append(bytes, router_info.options.Data()...)
//bytes = append(bytes, []byte(*router_info.signature)...)
return bytes, err
}
// RouterIdentity returns the router identity as *RouterIdentity.
func (router_info *RouterInfo) RouterIdentity() *RouterIdentity {
return router_info.router_identity
}
// IndentHash returns the identity hash (sha256 sum) for this RouterInfo.
func (router_info *RouterInfo) IdentHash() Hash {
ri := router_info.RouterIdentity()
h := HashData(ri.KeysAndCert.Certificate().Data())
return h
}
// Published returns the date this RouterInfo was published as an I2P Date.
func (router_info *RouterInfo) Published() *Date {
return router_info.published
}
// RouterAddressCount returns the count of RouterAddress in this RouterInfo as a Go integer.
func (router_info *RouterInfo) RouterAddressCount() int {
return router_info.size.Int()
}
// RouterAddresses returns all RouterAddresses for this RouterInfo as []*RouterAddress.
func (router_info *RouterInfo) RouterAddresses() []*RouterAddress {
return router_info.addresses
}
// PeerSize returns the peer size as a Go integer.
func (router_info *RouterInfo) PeerSize() int {
// Peer size is unused:
// https://geti2p.net/spec/common-structures#routeraddress
return 0
}
// Options returns the options for this RouterInfo as an I2P Mapping.
func (router_info RouterInfo) Options() (mapping Mapping) {
return *router_info.options
}
//
// Return the signature of this router info
//
// Signature returns the signature for this RouterInfo as an I2P Signature.
func (router_info RouterInfo) Signature() (signature Signature) {
return *router_info.signature
}
//
// Used during parsing to determine where in the RouterInfo the Mapping data begins.
//
/*func (router_info RouterInfo) optionsLocation() (location int) {
data, remainder, err := ReadRouterIdentity(router_info)
if err != nil {
return
}
location += len(data)
remainder_len := len(remainder)
if remainder_len < 9 {
log.WithFields(log.Fields{
"at": "(RouterInfo) optionsLocation",
"data_len": remainder_len,
"required_len": 9,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router addresses: not enough data")
return
}
location += 9
remaining := remainder[9:]
var router_address RouterAddress
var router_addresses []RouterAddress
addr_count, cerr := router_info.RouterAddressCount()
if cerr != nil {
err = cerr
return
}
for i := 0; i < addr_count; i++ {
router_address, remaining, err = ReadRouterAddress(remaining)
if err == nil {
location += len(router_address)
router_addresses = append(router_addresses, router_address)
}
}
location += 1
return
}*/
//
// Used during parsing to determine the size of the options in the RouterInfo.
//
/*func (router_info RouterInfo) optionsSize() (size int) {
head := router_info.optionsLocation()
s := Integer(router_info[head : head+2])
size = s.Int() + 2
return
}*/
// ReadRouterInfo returns RouterInfo from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadRouterInfo(bytes []byte) (info RouterInfo, remainder []byte, err error) {
identity, remainder, err := NewRouterIdentity(bytes)
info.router_identity = identity
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterInfo) ReadRouterInfo",
"data_len": len(bytes),
"required_len": ROUTER_INFO_MIN_SIZE,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router info: not enough data")
}
date, remainder, err := NewDate(remainder)
info.published = date
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterInfo) ReadRouterInfo",
"data_len": len(remainder),
"required_len": DATE_SIZE,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router info: not enough data")
}
size, remainder, err := NewInteger(remainder, 1)
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterInfo) ReadRouterInfo",
"data_len": len(remainder),
"required_len": size.Int(),
"reason": "read error",
}).Error("error parsing router info size")
}
info.size = size
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterInfo) ReadRouterInfo",
"data_len": len(remainder),
"required_len": size.Int(),
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router info: not enough data")
}
for i := 0; i < size.Int(); i++ {
address, more, err := NewRouterAddress(remainder)
remainder = more
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterInfo) ReadRouterInfo",
"data_len": len(remainder),
//"required_len": ROUTER_ADDRESS_SIZE,
"reason": "not enough data",
}).Error("error parsing router address")
err = errors.New("error parsing router info: not enough data")
}
info.addresses = append(info.addresses, address)
}
info.peer_size, remainder, err = NewInteger(remainder, 1)
var errs []error
info.options, remainder, errs = NewMapping(remainder)
if len(errs) != 0 {
log.WithFields(log.Fields{
"at": "(RouterInfo) ReadRouterInfo",
"data_len": len(remainder),
//"required_len": MAPPING_SIZE,
"reason": "not enough data",
}).Error("error parsing router info")
estring := ""
for _, e := range errs {
estring += e.Error() + " "
}
err = errors.New("error parsing router info: " + estring)
}
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterInfo) ReadRouterInfo",
"data_len": len(remainder),
//"required_len": MAPPING_SIZE,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router info: not enough data")
}
return
}
func (router_info *RouterInfo) RouterCapabilities() string {
str, err := ToI2PString("caps")
if err != nil {
return ""
}
return string(router_info.options.Values().Get(str))
}
func (router_info *RouterInfo) RouterVersion() string {
str, err := ToI2PString("router.version")
if err != nil {
return ""
}
return string(router_info.options.Values().Get(str))
}
func (router_info *RouterInfo) GoodVersion() bool {
version := router_info.RouterVersion()
v := strings.Split(version, ".")
if len(v) != 3 {
return false
}
if v[0] == "0" {
if v[1] == "9" {
val, _ := strconv.Atoi(v[2])
if val >= MIN_GOOD_VERSION && val <= MAX_GOOD_VERSION {
return true
}
}
}
return false
}
func (router_info *RouterInfo) UnCongested() bool {
caps := router_info.RouterCapabilities()
if strings.Contains(caps, "K") {
return false
}
if strings.Contains(caps, "G") {
return false
}
if strings.Contains(caps, "E") {
return false
}
return true
}
func (router_info *RouterInfo) Reachable() bool {
caps := router_info.RouterCapabilities()
if strings.Contains(caps, "U") {
return false
}
return strings.Contains(caps, "R")
}
// NewRouterInfo creates a new *RouterInfo from []byte using ReadRouterInfo.
// Returns a pointer to RouterInfo unlike ReadRouterInfo.
func NewRouterInfo(data []byte) (router_info *RouterInfo, remainder []byte, err error) {
routerInfo, remainder, err := ReadRouterInfo(data)
router_info = &routerInfo
return
}

View File

@ -0,0 +1,211 @@
package router_info
import (
"bytes"
"fmt"
"testing"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/router_address"
"github.com/go-i2p/go-i2p/lib/common/router_identity"
"github.com/stretchr/testify/assert"
)
func buildRouterIdentity() router_identity.RouterIdentity {
router_ident_data := make([]byte, 128+256)
router_ident_data = append(router_ident_data, []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}...)
return_data, _, _ := router_identity.ReadRouterIdentity(router_ident_data)
return return_data
}
func buildDate() []byte {
date_data := []byte{0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00}
return date_data
}
func buildMapping() *common.Mapping {
mapping, _ := common.GoMapToMapping(map[string]string{"host": "127.0.0.1", "port": "4567"})
return mapping
}
func buildRouterAddress(transport string) router_address.RouterAddress {
router_address_bytes := []byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
str, _ := common.ToI2PString(transport)
router_address_bytes = append(router_address_bytes, []byte(str)...)
router_address_bytes = append(router_address_bytes, buildMapping().Data()...)
return_data, _, _ := router_address.ReadRouterAddress(router_address_bytes)
return return_data
}
func buildFullRouterInfo(rid ...[]byte) (RouterInfo, error) {
var ri RouterInfo
var err error
if rid == nil || len(rid) == 0 {
router_info_data := make([]byte, 0)
router_info_data = append(router_info_data, buildRouterIdentity().KeysAndCert.Bytes()...)
router_info_data = append(router_info_data, buildDate()...)
router_info_data = append(router_info_data, 0x01)
router_info_data = append(router_info_data, buildRouterAddress("foo").Bytes()...)
router_info_data = append(router_info_data, 0x00)
router_info_data = append(router_info_data, buildMapping().Data()...)
router_info_data = append(router_info_data, make([]byte, 40)...)
ri, _, err = ReadRouterInfo(router_info_data)
} else {
ri, _, err = ReadRouterInfo(rid[0])
}
return ri, err
}
func TestPublishedReturnsCorrectDate(t *testing.T) {
assert := assert.New(t)
router_info, _ := buildFullRouterInfo()
date := router_info.Published()
assert.Equal(int64(86400), date.Time().Unix(), "RouterInfo.Published() did not return correct date")
}
func TestPublishedReturnsCorrectErrorWithPartialDate(t *testing.T) {
assert := assert.New(t)
router_info, err := buildFullRouterInfo()
assert.Nil(err)
bytes, err := router_info.Bytes()
router_info, err = buildFullRouterInfo(bytes[:387+4])
//_ := router_info.Published()
if assert.NotNil(err) {
assert.Equal("error parsing date: not enough data", err.Error())
}
}
func TestPublishedReturnsCorrectErrorWithInvalidData(t *testing.T) {
assert := assert.New(t)
router_info, err := buildFullRouterInfo()
if assert.Nil(err) {
bytes, err := router_info.Bytes()
router_info, err = buildFullRouterInfo(bytes[:56])
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
} else {
assert.Fail("error building router info")
}
}
func TestRouterAddressCountReturnsCorrectCount(t *testing.T) {
assert := assert.New(t)
router_info, _ := buildFullRouterInfo()
count := router_info.RouterAddressCount()
assert.Equal(1, count, "RouterInfo.RouterAddressCount() did not return correct count")
}
func TestRouterAddressCountReturnsCorrectErrorWithInvalidData(t *testing.T) {
assert := assert.New(t)
router_info, err := buildFullRouterInfo()
if assert.Nil(err) {
bytes, err := router_info.Bytes()
router_info, err = buildFullRouterInfo(bytes[:387+8])
count := router_info.RouterAddressCount()
if assert.NotNil(err) {
assert.Equal("error parsing router addresses: not enough data", err.Error())
}
assert.Equal(0, count)
}
}
func TestRouterAddressesReturnsAddresses(t *testing.T) {
assert := assert.New(t)
router_info, err := buildFullRouterInfo()
router_addresses := router_info.RouterAddresses()
assert.Nil(err)
if assert.Equal(1, len(router_addresses)) {
assert.Equal(
0,
bytes.Compare(
[]byte(buildRouterAddress("foo").Bytes()),
[]byte(router_addresses[0].Bytes()),
),
)
}
}
func TestRouterAddressesReturnsAddressesWithMultiple(t *testing.T) {
assert := assert.New(t)
router_info_data := make([]byte, 0)
router_info_data = append(router_info_data, buildRouterIdentity().KeysAndCert.Bytes()...)
router_info_data = append(router_info_data, buildDate()...)
router_info_data = append(router_info_data, 0x03)
router_info_data = append(router_info_data, buildRouterAddress("foo0").Bytes()...)
router_info_data = append(router_info_data, buildRouterAddress("foo1").Bytes()...)
router_info_data = append(router_info_data, buildRouterAddress("foo2").Bytes()...)
router_info_data = append(router_info_data, 0x00)
router_info_data = append(router_info_data, buildMapping().Data()...)
router_info_data = append(router_info_data, make([]byte, 40)...)
router_info, _, _ := ReadRouterInfo(router_info_data)
count := router_info.RouterAddressCount()
if assert.Equal(3, count) {
router_addresses := router_info.RouterAddresses()
for i := 0; i < 3; i++ {
assert.Equal(
0,
bytes.Compare(
[]byte(buildRouterAddress(fmt.Sprintf("foo%d", i)).Bytes()),
[]byte(router_addresses[i].Bytes()),
),
)
}
}
}
func TestPeerSizeIsZero(t *testing.T) {
assert := assert.New(t)
router_info, _ := buildFullRouterInfo()
size := router_info.PeerSize()
assert.Equal(0, size, "RouterInfo.PeerSize() did not return 0")
}
func TestOptionsAreCorrect(t *testing.T) {
assert := assert.New(t)
router_info, _ := buildFullRouterInfo()
options := router_info.Options()
assert.Equal(
0,
bytes.Compare(
[]byte(buildMapping().Data()),
[]byte(options.Data()),
),
)
}
func TestSignatureIsCorrectSize(t *testing.T) {
assert := assert.New(t)
router_info, _ := buildFullRouterInfo()
signature := router_info.Signature()
assert.Equal(40, len(signature))
}
func TestRouterIdentityIsCorrect(t *testing.T) {
assert := assert.New(t)
router_info, _ := buildFullRouterInfo()
router_identity := router_info.RouterIdentity()
//assert.Nil(err)
assert.Equal(
0,
bytes.Compare(
[]byte(buildRouterIdentity().KeysAndCert.Bytes()),
[]byte(router_identity.KeysAndCert.Bytes()),
),
)
}

View File

@ -1,191 +0,0 @@
package common
import (
"bytes"
"fmt"
"github.com/stretchr/testify/assert"
"testing"
)
func buildRouterIdentity() RouterIdentity {
router_ident_data := make([]byte, 128+256)
router_ident_data = append(router_ident_data, []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}...)
return RouterIdentity(router_ident_data)
}
func buildDate() []byte {
date_data := []byte{0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00}
return date_data
}
func buildMapping() Mapping {
mapping, _ := GoMapToMapping(map[string]string{"host": "127.0.0.1", "port": "4567"})
return mapping
}
func buildRouterAddress(transport string) RouterAddress {
router_address_bytes := []byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
str, _ := ToI2PString(transport)
router_address_bytes = append(router_address_bytes, []byte(str)...)
router_address_bytes = append(router_address_bytes, buildMapping()...)
return RouterAddress(router_address_bytes)
}
func buildFullRouterInfo() RouterInfo {
router_info_data := make([]byte, 0)
router_info_data = append(router_info_data, buildRouterIdentity()...)
router_info_data = append(router_info_data, buildDate()...)
router_info_data = append(router_info_data, 0x01)
router_info_data = append(router_info_data, buildRouterAddress("foo")...)
router_info_data = append(router_info_data, 0x00)
router_info_data = append(router_info_data, buildMapping()...)
router_info_data = append(router_info_data, make([]byte, 40)...)
return RouterInfo(router_info_data)
}
func TestPublishedReturnsCorrectDate(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
date, err := router_info.Published()
assert.Nil(err)
assert.Equal(int64(86400), date.Time().Unix(), "RouterInfo.Published() did not return correct date")
}
func TestPublishedReturnsCorrectErrorWithPartialDate(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
router_info = router_info[:387+4]
_, err := router_info.Published()
if assert.NotNil(err) {
assert.Equal("error parsing date: not enough data", err.Error())
}
}
func TestPublishedReturnsCorrectErrorWithInvalidData(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
router_info = router_info[:56]
_, err := router_info.Published()
if assert.NotNil(err) {
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
}
}
func TestRouterAddressCountReturnsCorrectCount(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
count, err := router_info.RouterAddressCount()
assert.Nil(err)
assert.Equal(1, count, "RouterInfo.RouterAddressCount() did not return correct count")
}
func TestRouterAddressCountReturnsCorrectErrorWithInvalidData(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
router_info = router_info[:387+8]
count, err := router_info.RouterAddressCount()
if assert.NotNil(err) {
assert.Equal("error parsing router addresses: not enough data", err.Error())
}
assert.Equal(0, count)
}
func TestRouterAddressesReturnsAddresses(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
router_addresses, err := router_info.RouterAddresses()
assert.Nil(err)
if assert.Equal(1, len(router_addresses)) {
assert.Equal(
0,
bytes.Compare(
[]byte(buildRouterAddress("foo")),
[]byte(router_addresses[0]),
),
)
}
}
func TestRouterAddressesReturnsAddressesWithMultiple(t *testing.T) {
assert := assert.New(t)
router_info_data := make([]byte, 0)
router_info_data = append(router_info_data, buildRouterIdentity()...)
router_info_data = append(router_info_data, buildDate()...)
router_info_data = append(router_info_data, 0x03)
router_info_data = append(router_info_data, buildRouterAddress("foo0")...)
router_info_data = append(router_info_data, buildRouterAddress("foo1")...)
router_info_data = append(router_info_data, buildRouterAddress("foo2")...)
router_info_data = append(router_info_data, 0x00)
router_info_data = append(router_info_data, buildMapping()...)
router_info_data = append(router_info_data, make([]byte, 40)...)
router_info := RouterInfo(router_info_data)
count, err := router_info.RouterAddressCount()
if assert.Equal(3, count) && assert.Nil(err) {
router_addresses, err := router_info.RouterAddresses()
if assert.Nil(err) {
for i := 0; i < 3; i++ {
assert.Equal(
0,
bytes.Compare(
[]byte(buildRouterAddress(fmt.Sprintf("foo%d", i))),
[]byte(router_addresses[i]),
),
)
}
}
}
}
func TestPeerSizeIsZero(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
size := router_info.PeerSize()
assert.Equal(0, size, "RouterInfo.PeerSize() did not return 0")
}
func TestOptionsAreCorrect(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
options := router_info.Options()
assert.Equal(
0,
bytes.Compare(
[]byte(buildMapping()),
[]byte(options),
),
)
}
func TestSignatureIsCorrectSize(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
signature := router_info.Signature()
assert.Equal(40, len(signature))
}
func TestRouterIdentityIsCorrect(t *testing.T) {
assert := assert.New(t)
router_info := buildFullRouterInfo()
router_identity, err := router_info.RouterIdentity()
assert.Nil(err)
assert.Equal(
0,
bytes.Compare(
[]byte(buildRouterIdentity()),
[]byte(router_identity),
),
)
}

View File

@ -1,3 +0,0 @@
package common
type SessionKey [32]byte

View File

@ -0,0 +1,34 @@
// Package session_key implements the I2P SessionKey common data structure
package session_key
/*
[SessionKey]
Accurate for version 0.9.49
Description
This structure is used for symmetric AES256 encryption and decryption.
Contents
32 bytes
*/
// SessionKey is the represenation of an I2P SessionKey.
//
// https://geti2p.net/spec/common-structures#sessionkey
type SessionKey [32]byte
// ReadSessionKey returns SessionKey from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadSessionKey(bytes []byte) (info SessionKey, remainder []byte, err error) {
// TODO: stub
return
}
// NewSessionKey creates a new *SessionKey from []byte using ReadSessionKey.
// Returns a pointer to SessionKey unlike ReadSessionKey.
func NewSessionKey(data []byte) (session_key *SessionKey, remainder []byte, err error) {
sessionKey, remainder, err := ReadSessionKey(data)
session_key = &sessionKey
return
}

View File

@ -1,3 +0,0 @@
package common
type SessionTag [32]byte

View File

@ -0,0 +1,34 @@
// Package session_tag implements the I2P SessionTag common data structure
package session_tag
/*
[SessionKey]
Accurate for version 0.9.49
Description
A random number
Contents
32 bytes
*/
// SessionTag is the represenation of an I2P SessionTag.
//
// https://geti2p.net/spec/common-structures#session-tag
type SessionTag [32]byte
// ReadSessionTag returns SessionTag from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadSessionTag(bytes []byte) (info SessionTag, remainder []byte, err error) {
// TODO: stub
return
}
// NewSessionTag creates a new *SessionTag from []byte using ReadSessionTag.
// Returns a pointer to SessionTag unlike ReadSessionTag.
func NewSessionTag(data []byte) (session_tag *SessionTag, remainder []byte, err error) {
sessionTag, remainder, err := ReadSessionTag(data)
session_tag = &sessionTag
return
}

View File

@ -1,3 +0,0 @@
package common
type Signature []byte

View File

@ -0,0 +1,49 @@
// Package signature implements the I2P Signature common data structure
package signature
// Lengths of signature keys
const (
DSA_SHA1_SIZE = 40
ECDSA_SHA256_P256_SIZE = 64
ECDSA_SHA384_P384_SIZE = 96
ECDSA_SHA512_P512_SIZE = 132
RSA_SHA256_2048_SIZE = 256
RSA_SHA384_3072_SIZE = 384
RSA_SHA512_4096_SIZE = 512
EdDSA_SHA512_Ed25519_SIZE = 64
EdDSA_SHA512_Ed25519ph_SIZE = 64
RedDSA_SHA512_Ed25519_SIZE = 64
)
/*
[Signature]
Accurate for version 0.9.49
Description
This structure represents the signature of some data.
Contents
Signature type and length are inferred from the type of key used. The default type is
DSA_SHA1. As of release 0.9.12, other types may be supported, depending on context.
*/
// Signature is the represenation of an I2P Signature.
//
// https://geti2p.net/spec/common-structures#signature
type Signature []byte
// ReadSignature returns Signature from a []byte.
// The remaining bytes after the specified length are also returned.
// Returns a list of errors that occurred during parsing.
func ReadSignature(bytes []byte) (info Signature, remainder []byte, err error) {
// TODO: stub
return
}
// NewSignature creates a new *Signature from []byte using ReadSignature.
// Returns a pointer to Signature unlike ReadSignature.
func NewSignature(data []byte) (session_tag *Signature, remainder []byte, err error) {
sessionTag, remainder, err := ReadSignature(data)
session_tag = &sessionTag
return
}

View File

@ -1,115 +0,0 @@
package common
/*
I2P String
https://geti2p.net/spec/common-structures#string
Accurate for version 0.9.24
*/
import (
"errors"
log "github.com/sirupsen/logrus"
)
// Maximum number of bytes that can be stored in an I2P string
const (
STRING_MAX_SIZE = 255
)
type String []byte
//
// Look up the length of the string, reporting errors if the string is
// invalid or the specified length does not match the provided data.
//
func (str String) Length() (length int, err error) {
if len(str) == 0 {
log.WithFields(log.Fields{
"at": "(String) Length",
"reason": "no data",
}).Error("error parsing string")
err = errors.New("error parsing string: zero length")
return
}
length = Integer([]byte{byte(str[0])})
inferred_len := length + 1
str_len := len(str)
if inferred_len > str_len {
log.WithFields(log.Fields{
"at": "(String) Length",
"string_bytes_length": str_len,
"string_length_field": length,
"expected_bytes_length": inferred_len,
"reason": "data shorter than specified",
}).Warn("string format warning")
err = errors.New("string parsing warning: string data is shorter than specified by length")
} else if str_len > inferred_len {
log.WithFields(log.Fields{
"at": "(String) Length",
"string_bytes_length": str_len,
"string_length_field": length,
"expected_bytes_length": inferred_len,
"reason": "data longer than specified",
}).Warn("string format warning")
err = errors.New("string parsing warning: string contains data beyond length")
}
return
}
//
// Return the string data and any errors encountered by Length.
//
func (str String) Data() (data string, err error) {
length, err := str.Length()
if err != nil {
switch err.Error() {
case "error parsing string: zero length":
return
case "string parsing warning: string data is shorter than specified by length":
data = string(str[1:])
return
case "string parsing warning: string contains data beyond length":
data = string(str[1 : length+1])
return
}
}
data = string(str[1:])
return
}
//
// This function takes an unformatted Go string and returns a String
// and any errors encountered during the encoding.
//
func ToI2PString(data string) (str String, err error) {
data_len := len(data)
if data_len > STRING_MAX_SIZE {
log.WithFields(log.Fields{
"at": "ToI2PString",
"string_len": data_len,
"max_len": STRING_MAX_SIZE,
"reason": "too much data",
}).Error("cannot create I2P string")
err = errors.New("cannot store that much data in I2P string")
return
}
i2p_string := []byte{byte(data_len)}
i2p_string = append(i2p_string, []byte(data)...)
str = String(i2p_string)
return
}
//
// Read a string from a slice of bytes, returning any extra data on the end
// of the slice and any errors encountered parsing the String.
//
func ReadString(data []byte) (str String, remainder []byte, err error) {
str = String(data)
length, err := String(data).Length()
if err != nil && err.Error() == "string parsing warning: string contains data beyond length" {
str = String(data[:length+1])
remainder = data[length+1:]
err = nil
}
return
}

View File

@ -1,583 +0,0 @@
package config
import (
"errors"
"github.com/go-i2p/go-i2p/lib/common"
log "github.com/sirupsen/logrus"
"strings"
"unicode/utf8"
)
//
// https://geti2p.net/spec/updates
//
const SU3_MAGIC_BYTES = "I2Psu3"
const SU3_MAGIC_BYTE_LEN = 6
const SU3_SIGNATURE_TYPE_LEN = 2
const SU3_SIGNATURE_LENGTH_LEN = 2
const SU3_CONTENT_LENGTH_LEN = 8
var SU3_SIGNATURE_TYPE_DSA_SHA1 = "DSA-SHA1"
var SU3_SIGNATURE_TYPE_ECDSA_SHA256_P256 = "ECDSA-SHA256-P256"
var SU3_SIGNATURE_TYPE_ECDSA_SHA384_P384 = "ECDSA-SHA384-P384"
var SU3_SIGNATURE_TYPE_ECDSA_SHA512_P521 = "ECDSA-SHA512-P521"
var SU3_SIGNATURE_TYPE_RSA_SHA256_2048 = "RSA-SHA256-2048"
var SU3_SIGNATURE_TYPE_RSA_SHA384_3072 = "RSA-SHA384-3072"
var SU3_SIGNATURE_TYPE_RSA_SHA512_4096 = "RSA-SHA512-4096"
var SU3_SIGNATURE_TYPE_EdDSA_SHA512_Ed25519ph = "EdDSA-SHA512-Ed25519ph"
var SU3_FILE_TYPE_ZIP = "zip"
var SU3_FILE_TYPE_XML = "xml"
var SU3_FILE_TYPE_HTML = "html"
var SU3_FILE_TYPE_XML_GZ = "xml.gz"
var SU3_FILE_TYPE_TXT_GZ = "txt.gz"
var SU3_CONTENT_TYPE_UNKNOWN = "unknown"
var SU3_CONTENT_TYPE_ROUTER_UPDATE = "router_update"
var SU3_CONTENT_TYPE_PLUGIN_UPDATE = "plugin_update"
var SU3_CONTENT_TYPE_RESEED_DATA = "reseed_data"
var SU3_CONTENT_TYPE_NEWS_FEED = "news_feed"
var SU3_CONTENT_TYPE_BLOCKLIST_FEED = "blocklist_feed"
var SU3_SIGNATURE_TYPE_MAP = map[[SU3_SIGNATURE_TYPE_LEN]byte]string{
{0x00, 0x00}: SU3_SIGNATURE_TYPE_DSA_SHA1,
{0x00, 0x01}: SU3_SIGNATURE_TYPE_ECDSA_SHA256_P256,
{0x00, 0x02}: SU3_SIGNATURE_TYPE_ECDSA_SHA384_P384,
{0x00, 0x03}: SU3_SIGNATURE_TYPE_ECDSA_SHA512_P521,
{0x00, 0x04}: SU3_SIGNATURE_TYPE_RSA_SHA256_2048,
{0x00, 0x05}: SU3_SIGNATURE_TYPE_RSA_SHA384_3072,
{0x00, 0x06}: SU3_SIGNATURE_TYPE_RSA_SHA512_4096,
{0x00, 0x08}: SU3_SIGNATURE_TYPE_EdDSA_SHA512_Ed25519ph,
}
var SU3_FILE_TYPE_MAP = map[byte]string{
0x00: SU3_FILE_TYPE_ZIP,
0x01: SU3_FILE_TYPE_XML,
0x02: SU3_FILE_TYPE_HTML,
0x03: SU3_FILE_TYPE_XML_GZ,
0x04: SU3_FILE_TYPE_TXT_GZ,
}
var SU3_CONTENT_TYPE_MAP = map[byte]string{
0x00: SU3_CONTENT_TYPE_UNKNOWN,
0x01: SU3_CONTENT_TYPE_ROUTER_UPDATE,
0x02: SU3_CONTENT_TYPE_PLUGIN_UPDATE,
0x03: SU3_CONTENT_TYPE_RESEED_DATA,
0x04: SU3_CONTENT_TYPE_NEWS_FEED,
0x05: SU3_CONTENT_TYPE_BLOCKLIST_FEED,
}
var ERR_NOT_ENOUGH_SU3_DATA = errors.New("not enough data for su3")
var ERR_SU3_MAGIC_BYTES_MISMATCH = errors.New("magic bytes do not match I2Psu3")
var ERR_SU3_UNUSED_BYTE_WITH_DATA = errors.New("unused byte in su3 specification contains data")
var ERR_SU3_SIGNATURE_TYPE_UNKNOWN = errors.New("unknown signature type")
var ERR_SU3_FILE_FORMAT_VERSION_UNKNOWN = errors.New("unknown file format version")
var ERR_SU3_VERSION_LENGTH_TOO_SMALL = errors.New("version length is too small")
var ERR_SU3_FILE_TYPE_UNKNOWN = errors.New("unknown file type")
var ERR_SU3_CONTENT_TYPE_UNKNOWN = errors.New("unknown content type")
var ERR_SU3_VERSION_NOT_UTF8 = errors.New("version not utf8")
var ERR_SU3_SIGNER_ID_NOT_UTF8 = errors.New("version not utf8")
type SU3 struct {
Raw []byte
FileFormatVersion int
SignatureType string
SignatureLength int
VersionLength int
SignerIDLength int
ContentLength int
FileType string
ContentType string
Version string
SignerID string
Content []byte
Signature []byte
}
func OpenSU3() {}
func ReadSU3(data []byte) (SU3, error) {
su3 := SU3{
Raw: data,
}
if err := checkMagicBytes(data); err != nil {
return su3, err
}
if err := checkByte6Unused(data); err != nil {
return su3, err
}
file_format_version, err := getFileFormatVersion(data)
su3.FileFormatVersion = file_format_version
if err != nil {
return su3, err
}
signature_type, err := getSignatureType(data)
su3.SignatureType = signature_type
if err != nil {
return su3, err
}
signature_length, err := getSignatureLength(data)
su3.SignatureLength = signature_length
if err != nil {
return su3, err
}
if err := checkByte12Unused(data); err != nil {
return su3, err
}
version_length, err := getVersionLength(data)
su3.VersionLength = version_length
if err != nil {
return su3, err
}
if err := checkByte14Unused(data); err != nil {
return su3, err
}
signer_id_length, err := getSignerIDLength(data)
su3.SignerIDLength = signer_id_length
if err != nil {
return su3, err
}
content_length, err := getContentLength(data)
su3.ContentLength = content_length
if err != nil {
return su3, err
}
file_type, err := getFileType(data)
su3.FileType = file_type
if err != nil {
return su3, err
}
if err := checkByte26Unused(data); err != nil {
return su3, err
}
content_type, err := getContentType(data)
su3.ContentType = content_type
if err != nil {
return su3, err
}
if err := checkBytes28To39Unused(data); err != nil {
return su3, err
}
version, err := getVersion(data)
su3.Version = version
if err != nil {
return su3, err
}
signer_id, err := getSignerID(data)
su3.SignerID = signer_id
if err != nil {
return su3, err
}
content, err := getContent(data)
su3.Content = content
if err != nil {
return su3, err
}
signature, err := getSignature(data)
su3.Signature = signature
if err != nil {
return su3, err
}
return su3, nil
}
func checkMagicBytes(data []byte) error {
if len(data) < SU3_MAGIC_BYTE_LEN {
return ERR_NOT_ENOUGH_SU3_DATA
}
magic_str := string(data[:SU3_MAGIC_BYTE_LEN])
if magic_str != SU3_MAGIC_BYTES {
log.WithFields(log.Fields{
"at": "config.checkMagicBytes",
"expected": []byte(SU3_MAGIC_BYTES),
"got": []byte(magic_str),
}).Debug(ERR_SU3_MAGIC_BYTES_MISMATCH)
return ERR_SU3_MAGIC_BYTES_MISMATCH
}
return nil
}
func checkByte6Unused(data []byte) error {
if len(data) < SU3_MAGIC_BYTE_LEN+1 {
return ERR_NOT_ENOUGH_SU3_DATA
}
unused_byte := data[SU3_MAGIC_BYTE_LEN]
if unused_byte != 0x00 {
log.WithFields(log.Fields{
"at": "config.checkByte6Unused",
"byte_data": unused_byte,
}).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA)
return ERR_SU3_UNUSED_BYTE_WITH_DATA
}
return nil
}
func getFileFormatVersion(data []byte) (int, error) {
if len(data) < SU3_MAGIC_BYTE_LEN+1+1 {
return 0, ERR_NOT_ENOUGH_SU3_DATA
}
if file_format_version_byte := data[SU3_MAGIC_BYTE_LEN+1]; file_format_version_byte != 0x00 {
log.WithFields(log.Fields{
"at": "config.getSignatureType",
"file_format_version_byte": file_format_version_byte,
}).Debug(ERR_SU3_FILE_FORMAT_VERSION_UNKNOWN)
return int(file_format_version_byte), ERR_SU3_FILE_FORMAT_VERSION_UNKNOWN
}
return 0, nil
}
func getSignatureType(data []byte) (string, error) {
signature_type := ""
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN {
return signature_type, ERR_NOT_ENOUGH_SU3_DATA
}
signature_type_bytes := [2]byte{}
copy(
signature_type_bytes[:],
data[SU3_MAGIC_BYTE_LEN+1+1:SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN],
)
if str, ok := SU3_SIGNATURE_TYPE_MAP[signature_type_bytes]; !ok {
log.WithFields(log.Fields{
"at": "config.getSignatureType",
"type": signature_type_bytes,
}).Debug(ERR_SU3_SIGNATURE_TYPE_UNKNOWN)
return signature_type, ERR_SU3_SIGNATURE_TYPE_UNKNOWN
} else {
signature_type = str
}
return signature_type, nil
}
func getSignatureLength(data []byte) (int, error) {
signature_length := 0
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN {
return signature_length, ERR_NOT_ENOUGH_SU3_DATA
}
signature_length_bytes := [2]byte{}
copy(
signature_length_bytes[:],
data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN:SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN],
)
signature_length = common.Integer(signature_length_bytes[:])
return signature_length, nil
}
func checkByte12Unused(data []byte) error {
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1 {
return ERR_NOT_ENOUGH_SU3_DATA
}
unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN]
if unused_byte != 0x00 {
log.WithFields(log.Fields{
"at": "config.checkByte12Unused",
"byte_data": unused_byte,
}).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA)
return ERR_SU3_UNUSED_BYTE_WITH_DATA
}
return nil
}
func getVersionLength(data []byte) (int, error) {
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1 {
return 0, ERR_NOT_ENOUGH_SU3_DATA
}
version_length := common.Integer([]byte{data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1]})
if version_length < 16 {
log.WithFields(log.Fields{
"at": "config.getSignatureType",
"version_length": version_length,
}).Debug(ERR_SU3_VERSION_LENGTH_TOO_SMALL)
return version_length, ERR_SU3_VERSION_LENGTH_TOO_SMALL
}
return version_length, nil
}
func checkByte14Unused(data []byte) error {
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1 {
return ERR_NOT_ENOUGH_SU3_DATA
}
unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1]
if unused_byte != 0x00 {
log.WithFields(log.Fields{
"at": "config.checkByte14Unused",
"byte_data": unused_byte,
}).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA)
return ERR_SU3_UNUSED_BYTE_WITH_DATA
}
return nil
}
func getSignerIDLength(data []byte) (int, error) {
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1 {
return 0, ERR_NOT_ENOUGH_SU3_DATA
}
signer_id_length := common.Integer([]byte{data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1]})
return signer_id_length, nil
}
func getContentLength(data []byte) (int, error) {
content_length := 0
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN {
return content_length, ERR_NOT_ENOUGH_SU3_DATA
}
content_length_bytes := [8]byte{}
copy(
content_length_bytes[:],
data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1:SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN],
)
content_length = common.Integer(content_length_bytes[:])
return content_length, nil
}
func checkByte24Unused(data []byte) error {
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1 {
return ERR_NOT_ENOUGH_SU3_DATA
}
unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN]
if unused_byte != 0x00 {
log.WithFields(log.Fields{
"at": "config.checkByte24Unused",
"byte_data": unused_byte,
}).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA)
return ERR_SU3_UNUSED_BYTE_WITH_DATA
}
return nil
}
func getFileType(data []byte) (string, error) {
file_type := ""
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1 {
return file_type, ERR_NOT_ENOUGH_SU3_DATA
}
file_type_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1]
if str, ok := SU3_FILE_TYPE_MAP[file_type_byte]; !ok {
log.WithFields(log.Fields{
"at": "config.getFileType",
"type": file_type_byte,
}).Debug(ERR_SU3_FILE_TYPE_UNKNOWN)
return file_type, ERR_SU3_FILE_TYPE_UNKNOWN
} else {
file_type = str
}
return file_type, nil
}
func checkByte26Unused(data []byte) error {
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1+1 {
return ERR_NOT_ENOUGH_SU3_DATA
}
unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1]
if unused_byte != 0x00 {
log.WithFields(log.Fields{
"at": "config.checkByt26Unused",
"byte_data": unused_byte,
}).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA)
return ERR_SU3_UNUSED_BYTE_WITH_DATA
}
return nil
}
func getContentType(data []byte) (string, error) {
content_type := ""
if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1+1+1 {
return content_type, ERR_NOT_ENOUGH_SU3_DATA
}
content_type_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1+1]
if str, ok := SU3_CONTENT_TYPE_MAP[content_type_byte]; !ok {
log.WithFields(log.Fields{
"at": "config.getContentType",
"type": content_type_byte,
}).Debug(ERR_SU3_CONTENT_TYPE_UNKNOWN)
return content_type, ERR_SU3_CONTENT_TYPE_UNKNOWN
} else {
content_type = str
}
return content_type, nil
}
func checkBytes28To39Unused(data []byte) error {
end := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12
if len(data) < end {
return ERR_NOT_ENOUGH_SU3_DATA
}
unused_bytes := [12]byte{}
copy(
unused_bytes[:],
data[end-12:end],
)
for i, value := range unused_bytes {
if value != 0x00 {
log.WithFields(log.Fields{
"at": "config.checkBytes28To39Unused",
"iterator": i,
}).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA)
return ERR_SU3_UNUSED_BYTE_WITH_DATA
}
}
return nil
}
func getVersion(data []byte) (string, error) {
version := ""
version_length, err := getVersionLength(data)
if err != nil {
return version, err
}
min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12
if len(data) < min+version_length {
return version, ERR_NOT_ENOUGH_SU3_DATA
}
version_bytes := data[min : min+version_length]
version_str := strings.TrimRight(string(version_bytes), "\x00")
if !utf8.ValidString(version_str) {
log.WithFields(log.Fields{
"at": "config.getVersion",
}).Debug(ERR_SU3_VERSION_NOT_UTF8)
return version, ERR_SU3_VERSION_NOT_UTF8
} else {
version = version_str
}
return version, nil
}
func getSignerID(data []byte) (string, error) {
signer_id := ""
signer_id_length, err := getSignerIDLength(data)
if err != nil {
return signer_id, err
}
version_length, err := getVersionLength(data)
if err != nil {
return signer_id, err
}
min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 + version_length
if len(data) < min+signer_id_length {
return signer_id, ERR_NOT_ENOUGH_SU3_DATA
}
signer_id_bytes := data[min : min+signer_id_length]
signer_id_str := string(signer_id_bytes)
if !utf8.ValidString(signer_id_str) {
log.WithFields(log.Fields{
"at": "config.getSignerID",
}).Debug(ERR_SU3_SIGNER_ID_NOT_UTF8)
return signer_id, ERR_SU3_SIGNER_ID_NOT_UTF8
} else {
signer_id = signer_id_str
}
return signer_id, nil
}
func getContent(data []byte) ([]byte, error) {
content := []byte{}
content_length, err := getContentLength(data)
if err != nil {
return content, err
}
signer_id_length, err := getSignerIDLength(data)
if err != nil {
return content, err
}
version_length, err := getVersionLength(data)
if err != nil {
return content, err
}
min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 + version_length + signer_id_length
if len(data) < min+content_length {
return content, ERR_NOT_ENOUGH_SU3_DATA
}
content = data[min : min+content_length]
return content, nil
}
func getSignature(data []byte) ([]byte, error) {
signature := []byte{}
signature_length, err := getSignatureLength(data)
if err != nil {
return signature, err
}
content_length, err := getContentLength(data)
if err != nil {
return signature, err
}
signer_id_length, err := getSignerIDLength(data)
if err != nil {
return signature, err
}
version_length, err := getVersionLength(data)
if err != nil {
return signature, err
}
min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 + version_length + signer_id_length + content_length
if len(data) < min+signature_length {
return signature, ERR_NOT_ENOUGH_SU3_DATA
}
signature = data[min : min+signature_length]
return signature, nil
}

View File

@ -1,458 +0,0 @@
package config
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestCheckMagicBytes(t *testing.T) {
assert := assert.New(t)
assert.Equal(
nil,
checkMagicBytes([]byte("I2Psu3")),
)
assert.Equal(
ERR_NOT_ENOUGH_SU3_DATA,
checkMagicBytes([]byte("I2Psu")),
)
assert.Equal(
ERR_SU3_MAGIC_BYTES_MISMATCH,
checkMagicBytes([]byte("I2Psu4")),
)
}
func TestCheckByte6Unused(t *testing.T) {
assert := assert.New(t)
assert.Equal(
nil,
checkByte6Unused(append([]byte("I2Psu3"), 0x00)),
)
assert.Equal(
ERR_NOT_ENOUGH_SU3_DATA,
checkByte6Unused([]byte("I2Psu3")),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkByte6Unused(append([]byte("I2Psu3"), 0x41)),
)
}
func TestGetFileFormatVersion(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), 0x00)
file_format_version, err := getFileFormatVersion(append(su3_base, 0x00))
assert.Equal(nil, err)
assert.Equal(0, file_format_version)
file_format_version, err = getFileFormatVersion(su3_base)
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal(0, file_format_version)
file_format_version, err = getFileFormatVersion(append(su3_base, 0x01))
assert.Equal(ERR_SU3_FILE_FORMAT_VERSION_UNKNOWN, err)
assert.Equal(1, file_format_version)
}
func TestGetSignatureType(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00}...)
sig_type, err := getSignatureType(append(su3_base, []byte{0x00, 0x00}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_DSA_SHA1, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x01}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_ECDSA_SHA256_P256, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x02}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_ECDSA_SHA384_P384, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x03}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_ECDSA_SHA512_P521, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x04}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_RSA_SHA256_2048, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x05}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_RSA_SHA384_3072, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x06}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_RSA_SHA512_4096, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x08}...))
assert.Equal(nil, err)
assert.Equal(SU3_SIGNATURE_TYPE_EdDSA_SHA512_Ed25519ph, sig_type)
sig_type, err = getSignatureType(append(su3_base, []byte{0x00, 0x07}...))
assert.Equal(ERR_SU3_SIGNATURE_TYPE_UNKNOWN, err)
assert.Equal("", sig_type)
sig_type, err = getSignatureType(append(su3_base, 0x00))
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal("", sig_type)
}
func TestGetSignatureLength(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00}...)
sig_len, err := getSignatureLength(append(su3_base, []byte{0x00, 0x00, 0x00, 0x28}...))
assert.Equal(nil, err)
assert.Equal(40, sig_len)
sig_len, err = getSignatureLength(append(su3_base, []byte{0x00, 0x00, 0x00}...))
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal(0, sig_len)
}
func TestCheckByte12Unused(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28}...)
assert.Equal(
nil,
checkByte12Unused(append(su3_base, 0x00)),
)
assert.Equal(
ERR_NOT_ENOUGH_SU3_DATA,
checkByte12Unused(su3_base),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkByte12Unused(append(su3_base, 0x41)),
)
}
func TestGetVersionLength(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00}...)
version_length, err := getVersionLength(append(su3_base, 0x10))
assert.Equal(nil, err)
assert.Equal(16, version_length)
version_length, err = getVersionLength(su3_base)
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal(0, version_length)
version_length, err = getVersionLength(append(su3_base, 0x01))
assert.Equal(ERR_SU3_VERSION_LENGTH_TOO_SMALL, err)
assert.Equal(1, version_length)
}
func TestCheckByte14Unused(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10}...)
assert.Equal(
nil,
checkByte14Unused(append(su3_base, 0x00)),
)
assert.Equal(
ERR_NOT_ENOUGH_SU3_DATA,
checkByte14Unused(su3_base),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkByte14Unused(append(su3_base, 0x41)),
)
}
func TestGetSignerIDLength(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x00}...)
signer_id_length, err := getSignerIDLength(append(su3_base, 0x10))
assert.Equal(nil, err)
assert.Equal(16, signer_id_length)
signer_id_length, err = getSignerIDLength(su3_base)
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal(0, signer_id_length)
}
func TestGetContentLength(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10}...)
content_length, err := getContentLength(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}...))
assert.Equal(nil, err)
assert.Equal(1, content_length)
content_length, err = getContentLength(append(su3_base, []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...))
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal(0, content_length)
}
func TestCheckByte24Unused(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}...)
assert.Equal(
nil,
checkByte24Unused(append(su3_base, 0x00)),
)
assert.Equal(
ERR_NOT_ENOUGH_SU3_DATA,
checkByte24Unused(su3_base),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkByte24Unused(append(su3_base, 0x41)),
)
}
func TestGetFileType(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}...)
sig_type, err := getFileType(append(su3_base, 0x00))
assert.Equal(nil, err)
assert.Equal(SU3_FILE_TYPE_ZIP, sig_type)
sig_type, err = getFileType(append(su3_base, 0x01))
assert.Equal(nil, err)
assert.Equal(SU3_FILE_TYPE_XML, sig_type)
sig_type, err = getFileType(append(su3_base, 0x02))
assert.Equal(nil, err)
assert.Equal(SU3_FILE_TYPE_HTML, sig_type)
sig_type, err = getFileType(append(su3_base, 0x03))
assert.Equal(nil, err)
assert.Equal(SU3_FILE_TYPE_XML_GZ, sig_type)
sig_type, err = getFileType(append(su3_base, 0x04))
assert.Equal(nil, err)
assert.Equal(SU3_FILE_TYPE_TXT_GZ, sig_type)
sig_type, err = getFileType(append(su3_base, 0x05))
assert.Equal(ERR_SU3_FILE_TYPE_UNKNOWN, err)
assert.Equal("", sig_type)
sig_type, err = getFileType(su3_base)
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal("", sig_type)
}
func TestCheckByte26Unused(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}...)
assert.Equal(
nil,
checkByte26Unused(append(su3_base, 0x00)),
)
assert.Equal(
ERR_NOT_ENOUGH_SU3_DATA,
checkByte26Unused(su3_base),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkByte26Unused(append(su3_base, 0x41)),
)
}
func TestGetContextType(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}...)
content_type, err := getContentType(append(su3_base, 0x00))
assert.Equal(nil, err)
assert.Equal(SU3_CONTENT_TYPE_UNKNOWN, content_type)
content_type, err = getContentType(append(su3_base, 0x01))
assert.Equal(nil, err)
assert.Equal(SU3_CONTENT_TYPE_ROUTER_UPDATE, content_type)
content_type, err = getContentType(append(su3_base, 0x02))
assert.Equal(nil, err)
assert.Equal(SU3_CONTENT_TYPE_PLUGIN_UPDATE, content_type)
content_type, err = getContentType(append(su3_base, 0x03))
assert.Equal(nil, err)
assert.Equal(SU3_CONTENT_TYPE_RESEED_DATA, content_type)
content_type, err = getContentType(append(su3_base, 0x04))
assert.Equal(nil, err)
assert.Equal(SU3_CONTENT_TYPE_NEWS_FEED, content_type)
content_type, err = getContentType(append(su3_base, 0x05))
assert.Equal(nil, err)
assert.Equal(SU3_CONTENT_TYPE_BLOCKLIST_FEED, content_type)
content_type, err = getContentType(append(su3_base, 0x06))
assert.Equal(ERR_SU3_CONTENT_TYPE_UNKNOWN, err)
assert.Equal("", content_type)
content_type, err = getContentType(su3_base)
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal("", content_type)
}
func TestCheckBytes28To39Unused(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}...)
assert.Equal(
nil,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_NOT_ENOUGH_SU3_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}...)),
)
assert.Equal(
ERR_SU3_UNUSED_BYTE_WITH_DATA,
checkBytes28To39Unused(append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}...)),
)
}
func TestGetVersion(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
version, err := getVersion(append(su3_base, []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...))
assert.Equal(nil, err)
assert.Equal("A", version)
version, err = getVersion(append(su3_base, []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...))
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal("", version)
su3_base[13] = 0x11
version, err = getVersion(append(su3_base, []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41}...))
assert.Equal(nil, err)
assert.Equal("AAAAAAAAAAAAAAAAA", version)
}
func TestGetSignerID(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
signer_id, err := getSignerID(append(su3_base, 0x41))
assert.Equal(nil, err)
assert.Equal("A", signer_id)
signer_id, err = getSignerID(su3_base)
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal("", signer_id)
}
func TestGetContent(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, 0x41)
content, err := getContent(append(su3_base, 0x42))
assert.Equal(nil, err)
assert.Equal("B", string(content))
su3_base[23] = 0x02
content, err = getContent(append(su3_base, []byte{0x42, 0x42}...))
assert.Equal(nil, err)
assert.Equal("BB", string(content))
content, err = getContent(su3_base)
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal([]byte{}, content)
}
func TestGetSignature(t *testing.T) {
assert := assert.New(t)
su3_base := append([]byte("I2Psu3"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x10, 0x000, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
su3_base = append(su3_base, []byte{0x41, 0x42}...)
signature, err := getSignature(append(su3_base, make([]byte, 40)...))
assert.Equal(nil, err)
assert.Equal(make([]byte, 40), signature)
signature, err = getSignature(append(su3_base, make([]byte, 39)...))
assert.Equal(ERR_NOT_ENOUGH_SU3_DATA, err)
assert.Equal([]byte{}, signature)
}

View File

@ -1,4 +1,2 @@
//
// package for i2p specific crpytography
//
package crypto

View File

@ -1,54 +1,114 @@
package crypto
/*
#cgo pkg-config: libsodium
#include <sodium.h>
#include <stdint.h>
*/
import "C"
import (
"crypto/ed25519"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
"io"
"math/big"
)
type Ed25519PublicKey [32]byte
var Ed25519EncryptTooBig = errors.New("failed to encrypt data, too big for Ed25519")
type Ed25519PublicKey []byte
type Ed25519Verifier struct {
k [32]C.uchar
k []byte
}
func (k Ed25519PublicKey) NewVerifier() (v Verifier, err error) {
ev := new(Ed25519Verifier)
for i, b := range k {
ev.k[i] = C.uchar(b)
temp := new(Ed25519Verifier)
temp.k = k
v = temp
return temp, nil
}
func (k Ed25519PublicKey) Len() int {
return len(k)
}
func createEd25519PublicKey(data []byte) (k *ed25519.PublicKey) {
if len(data) == 256 {
k2 := ed25519.PublicKey{}
copy(k2[:], data)
k = &k2
}
v = ev
return
}
func createEd25519Encryption(pub *ed25519.PublicKey, rand io.Reader) (enc *Ed25519Encryption, err error) {
/*kbytes := make([]byte, 256)
k := new(big.Int)
for err == nil {
_, err = io.ReadFull(rand, kbytes)
k = new(big.Int).SetBytes(kbytes)
k = k.Mod(k, pub.P)
if k.Sign() != 0 {
break
}
}
if err == nil {
enc = &Ed25519Encryption{}
}*/
return
}
type Ed25519Encryption struct {
p, a, b1 *big.Int
}
func (ed25519 *Ed25519Encryption) Encrypt(data []byte) (enc []byte, err error) {
return ed25519.EncryptPadding(data, true)
}
func (ed25519 *Ed25519Encryption) EncryptPadding(data []byte, zeroPadding bool) (encrypted []byte, err error) {
if len(data) > 222 {
err = Ed25519EncryptTooBig
return
}
mbytes := make([]byte, 255)
mbytes[0] = 0xFF
copy(mbytes[33:], data)
// do sha256 of payload
d := sha256.Sum256(mbytes[33 : len(data)+33])
copy(mbytes[1:], d[:])
m := new(big.Int).SetBytes(mbytes)
// do encryption
b := new(big.Int).Mod(new(big.Int).Mul(ed25519.b1, m), ed25519.p).Bytes()
if zeroPadding {
encrypted = make([]byte, 514)
copy(encrypted[1:], ed25519.a.Bytes())
copy(encrypted[258:], b)
} else {
encrypted = make([]byte, 512)
copy(encrypted, ed25519.a.Bytes())
copy(encrypted[256:], b)
}
return
}
func (elg Ed25519PublicKey) NewEncrypter() (enc Encrypter, err error) {
k := createEd25519PublicKey(elg[:])
enc, err = createEd25519Encryption(k, rand.Reader)
return
}
func (v *Ed25519Verifier) VerifyHash(h, sig []byte) (err error) {
if len(sig) == C.crypto_sign_BYTES {
// valid size of sig
// copy signature and hash
var csig, ch [32]C.uchar
for i, b := range h {
ch[i] = C.uchar(b)
}
for i, b := range sig {
csig[i] = C.uchar(b)
}
// verify
if C.crypto_sign_verify_detached(&csig[0], &ch[0], C.ulonglong(32), &v.k[0]) == 0 {
// valid signature
} else {
// bad signature
err = ErrInvalidSignature
}
} else {
// bad size of sig
if len(sig) != ed25519.SignatureSize {
err = ErrBadSignatureSize
return
}
if len(v.k) != ed25519.PublicKeySize {
err = errors.New("failed to verify: invalid ed25519 public key size")
return
}
ok := ed25519.Verify(v.k, h, sig)
if !ok {
err = errors.New("failed to verify: invalid signature")
}
return
}
@ -59,35 +119,23 @@ func (v *Ed25519Verifier) Verify(data, sig []byte) (err error) {
return
}
type Ed25519PrivateKey [32]byte
type Ed25519PrivateKey ed25519.PrivateKey
type Ed25519Signer struct {
k [32]C.uchar
k []byte
}
func (s *Ed25519Signer) Sign(data []byte) (sig []byte, err error) {
if len(s.k) != ed25519.PrivateKeySize {
err = errors.New("failed to sign: invalid ed25519 private key size")
return
}
h := sha512.Sum512(data)
sig, err = s.SignHash(h[:])
return
}
func (s *Ed25519Signer) SignHash(h []byte) (sig []byte, err error) {
var ch [32]C.uchar
for i, b := range h {
ch[i] = C.uchar(b)
}
var csig [32]C.uchar
var smlen_p C.ulonglong
res := C.crypto_sign_detached(&csig[0], &smlen_p, &ch[0], C.ulonglong(32), &s.k[0])
if res == 0 {
// success signing
sig = make([]byte, 32)
for i, b := range csig {
sig[i] = byte(b)
}
} else {
// failed signing
err = errors.New(fmt.Sprintf("failed to sign: crypto_sign_detached exit code %d", int(res)))
}
sig = ed25519.Sign(s.k, h)
return
}

View File

@ -1,9 +1,42 @@
package crypto
import (
"crypto/ed25519"
"crypto/rand"
"io"
"testing"
)
func TestEd25519(t *testing.T) {
var pubKey Ed25519PublicKey
signer := new(Ed25519Signer)
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Log("Failed to generate ed25519 test key")
t.Fail()
}
pubKey = []byte(pub)
signer.k = []byte(priv)
message := make([]byte, 123)
io.ReadFull(rand.Reader, message)
sig, err := signer.Sign(message)
if err != nil {
t.Log("Failed to sign message")
t.Fail()
}
verifier, err := pubKey.NewVerifier()
if err != nil {
t.Logf("Error from verifier: %s", err)
t.Fail()
}
err = verifier.Verify(message, sig)
if err != nil {
t.Log("Failed to verify message")
t.Fail()
}
}

View File

@ -5,9 +5,10 @@ import (
"crypto/sha256"
"crypto/subtle"
"errors"
"golang.org/x/crypto/openpgp/elgamal"
"io"
"math/big"
"golang.org/x/crypto/openpgp/elgamal"
)
var elgp = new(big.Int).SetBytes([]byte{

View File

@ -23,9 +23,7 @@ func (hk HMACKey) xor(p byte) (i []byte) {
return
}
//
// do i2p hmac
//
func I2PHMAC(data []byte, k HMACKey) (d HMACDigest) {
buff := make([]byte, 64+len(data))

View File

@ -2,10 +2,12 @@ package i2np
import (
"errors"
"github.com/go-i2p/go-i2p/lib/common"
"time"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/session_key"
"github.com/go-i2p/go-i2p/lib/tunnel"
log "github.com/sirupsen/logrus"
"time"
)
/*
@ -155,9 +157,9 @@ type BuildRequestRecord struct {
OurIdent common.Hash
NextTunnel tunnel.TunnelID
NextIdent common.Hash
LayerKey common.SessionKey
IVKey common.SessionKey
ReplyKey common.SessionKey
LayerKey session_key.SessionKey
IVKey session_key.SessionKey
ReplyKey session_key.SessionKey
ReplyIV [16]byte
Flag int
RequestTime time.Time
@ -251,7 +253,7 @@ func readBuildRequestRecordReceiveTunnel(data []byte) (tunnel.TunnelID, error) {
}
receive_tunnel := tunnel.TunnelID(
common.Integer(data[0:4]),
common.Integer(data[0:4]).Int(),
)
log.WithFields(log.Fields{
@ -281,7 +283,7 @@ func readBuildRequestRecordNextTunnel(data []byte) (tunnel.TunnelID, error) {
}
next_tunnel := tunnel.TunnelID(
common.Integer(data[36:40]),
common.Integer(data[36:40]).Int(),
)
log.WithFields(log.Fields{
@ -305,12 +307,12 @@ func readBuildRequestRecordNextIdent(data []byte) (common.Hash, error) {
return hash, nil
}
func readBuildRequestRecordLayerKey(data []byte) (common.SessionKey, error) {
func readBuildRequestRecordLayerKey(data []byte) (session_key.SessionKey, error) {
if len(data) < 104 {
return common.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
return session_key.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
}
session_key := common.SessionKey{}
session_key := session_key.SessionKey{}
copy(session_key[:], data[72:104])
log.WithFields(log.Fields{
@ -319,12 +321,12 @@ func readBuildRequestRecordLayerKey(data []byte) (common.SessionKey, error) {
return session_key, nil
}
func readBuildRequestRecordIVKey(data []byte) (common.SessionKey, error) {
func readBuildRequestRecordIVKey(data []byte) (session_key.SessionKey, error) {
if len(data) < 136 {
return common.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
return session_key.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
}
session_key := common.SessionKey{}
session_key := session_key.SessionKey{}
copy(session_key[:], data[104:136])
log.WithFields(log.Fields{
@ -333,12 +335,12 @@ func readBuildRequestRecordIVKey(data []byte) (common.SessionKey, error) {
return session_key, nil
}
func readBuildRequestRecordReplyKey(data []byte) (common.SessionKey, error) {
func readBuildRequestRecordReplyKey(data []byte) (session_key.SessionKey, error) {
if len(data) < 168 {
return common.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
return session_key.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
}
session_key := common.SessionKey{}
session_key := session_key.SessionKey{}
copy(session_key[:], data[136:168])
log.WithFields(log.Fields{
@ -366,7 +368,7 @@ func readBuildRequestRecordFlag(data []byte) (int, error) {
return 0, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
}
flag := int(common.Integer([]byte{data[185]}))
flag := common.Integer([]byte{data[185]}).Int()
log.WithFields(log.Fields{
"at": "i2np.readBuildRequestRecordFlag",
@ -380,7 +382,7 @@ func readBuildRequestRecordRequestTime(data []byte) (time.Time, error) {
return time.Time{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
}
count := int(common.Integer(data[185:189]))
count := common.Integer(data[185:189]).Int()
rtime := time.Unix(0, 0).Add(time.Duration(count) * time.Hour)
log.WithFields(log.Fields{
@ -394,7 +396,7 @@ func readBuildRequestRecordSendMessageID(data []byte) (int, error) {
return 0, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
}
send_message_id := int(common.Integer(data[189:193]))
send_message_id := common.Integer(data[189:193]).Int()
log.WithFields(log.Fields{
"at": "i2np.readBuildRequestRecordSendMessageID",

View File

@ -1,10 +1,11 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
"testing"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/tunnel"
"github.com/stretchr/testify/assert"
"testing"
)
func TestReadBuildRequestRecordReceiveTunnelTooLittleData(t *testing.T) {

View File

@ -1,7 +1,7 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
common "github.com/go-i2p/go-i2p/lib/common/data"
)
/*

View File

@ -1,7 +1,9 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/session_key"
"github.com/go-i2p/go-i2p/lib/common/session_tag"
)
/*
@ -143,7 +145,7 @@ type DatabaseLookup struct {
ReplyTunnelID [4]byte
Size int
ExcludedPeers []common.Hash
ReplyKey common.SessionKey
ReplyKey session_key.SessionKey
tags int
ReplyTags []common.SessionTag
ReplyTags []session_tag.SessionTag
}

View File

@ -1,7 +1,7 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
common "github.com/go-i2p/go-i2p/lib/common/data"
)
/*

View File

@ -1,7 +1,7 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
common "github.com/go-i2p/go-i2p/lib/common/data"
)
/*

View File

@ -1,8 +1,9 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
"time"
"github.com/go-i2p/go-i2p/lib/common/certificate"
)
/*
@ -66,7 +67,7 @@ type GarlicElGamal []byte
type Garlic struct {
Count int
Cloves []GarlicClove
Certificate common.Certificate
Certificate certificate.Certificate
MessageID int
Expiration time.Time
}

View File

@ -1,8 +1,9 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
"time"
"github.com/go-i2p/go-i2p/lib/common/certificate"
)
/*
@ -45,5 +46,5 @@ type GarlicClove struct {
I2NPMessage I2NPMessage
CloveID int
Expiration time.Time
Certificate common.Certificate
Certificate certificate.Certificate
}

View File

@ -1,7 +1,8 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/session_key"
"github.com/go-i2p/go-i2p/lib/tunnel"
)
@ -73,7 +74,7 @@ Total length: Typical length is:
type GarlicCloveDeliveryInstructions struct {
Flag byte
SessionKey common.SessionKey
SessionKey session_key.SessionKey
Hash common.Hash
TunnelID tunnel.TunnelID
Delay int

View File

@ -2,9 +2,11 @@ package i2np
import (
"errors"
"github.com/go-i2p/go-i2p/lib/common"
log "github.com/sirupsen/logrus"
"time"
datalib "github.com/go-i2p/go-i2p/lib/common/data"
log "github.com/sirupsen/logrus"
)
/*
@ -168,24 +170,24 @@ func ReadI2NPType(data []byte) (int, error) {
return 0, ERR_I2NP_NOT_ENOUGH_DATA
}
message_type := common.Integer([]byte{data[0]})
message_type := datalib.Integer([]byte{data[0]})
if (message_type >= 4 || message_type <= 9) ||
(message_type >= 12 || message_type <= 17) {
if (message_type.Int() >= 4 || message_type.Int() <= 9) ||
(message_type.Int() >= 12 || message_type.Int() <= 17) {
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPType",
"type": message_type,
}).Warn("unknown_i2np_type")
}
if message_type >= 224 || message_type <= 254 {
if message_type.Int() >= 224 || message_type.Int() <= 254 {
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPType",
"type": message_type,
}).Warn("experimental_i2np_type")
}
if message_type == 255 {
if message_type.Int() == 255 {
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPType",
"type": message_type,
@ -196,7 +198,7 @@ func ReadI2NPType(data []byte) (int, error) {
"at": "i2np.ReadI2NPType",
"type": message_type,
}).Debug("parsed_i2np_type")
return message_type, nil
return message_type.Int(), nil
}
func ReadI2NPNTCPMessageID(data []byte) (int, error) {
@ -204,21 +206,21 @@ func ReadI2NPNTCPMessageID(data []byte) (int, error) {
return 0, ERR_I2NP_NOT_ENOUGH_DATA
}
message_id := common.Integer(data[1:5])
message_id := datalib.Integer(data[1:5])
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPNTCPMessageID",
"type": message_id,
}).Debug("parsed_i2np_message_id")
return message_id, nil
return message_id.Int(), nil
}
func ReadI2NPNTCPMessageExpiration(data []byte) (common.Date, error) {
func ReadI2NPNTCPMessageExpiration(data []byte) (datalib.Date, error) {
if len(data) < 13 {
return common.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
return datalib.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
}
date := common.Date{}
date := datalib.Date{}
copy(date[:], data[5:13])
log.WithFields(log.Fields{
@ -228,12 +230,12 @@ func ReadI2NPNTCPMessageExpiration(data []byte) (common.Date, error) {
return date, nil
}
func ReadI2NPSSUMessageExpiration(data []byte) (common.Date, error) {
func ReadI2NPSSUMessageExpiration(data []byte) (datalib.Date, error) {
if len(data) < 5 {
return common.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
return datalib.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
}
date := common.Date{}
date := datalib.Date{}
copy(date[4:], data[1:5])
log.WithFields(log.Fields{
@ -248,13 +250,13 @@ func ReadI2NPNTCPMessageSize(data []byte) (int, error) {
return 0, ERR_I2NP_NOT_ENOUGH_DATA
}
size := common.Integer(data[13:15])
size := datalib.Integer(data[13:15])
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPNTCPMessageSize",
"size": size,
}).Debug("parsed_i2np_message_size")
return size, nil
return size.Int(), nil
}
func ReadI2NPNTCPMessageChecksum(data []byte) (int, error) {
@ -262,13 +264,13 @@ func ReadI2NPNTCPMessageChecksum(data []byte) (int, error) {
return 0, ERR_I2NP_NOT_ENOUGH_DATA
}
checksum := common.Integer(data[15:16])
checksum := datalib.Integer(data[15:16])
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPNTCPMessageCHecksum",
"checksum": checksum,
}).Debug("parsed_i2np_message_checksum")
return checksum, nil
return checksum.Int(), nil
}
func ReadI2NPNTCPData(data []byte, size int) ([]byte, error) {

View File

@ -1,9 +1,10 @@
package i2np
import (
"github.com/go-i2p/go-i2p/lib/common"
"github.com/stretchr/testify/assert"
"testing"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/stretchr/testify/assert"
)
func TestReadI2NPTypeWithNoData(t *testing.T) {

View File

@ -1,5 +1 @@
//
//
//
//
package netdb

View File

@ -1,14 +1,15 @@
package netdb
import (
"github.com/go-i2p/go-i2p/lib/common"
"io"
"github.com/go-i2p/go-i2p/lib/common/router_info"
)
// netdb entry
// wraps a router info and provides serialization
type Entry struct {
ri common.RouterInfo
ri router_info.RouterInfo
}
func (e *Entry) WriteTo(w io.Writer) (err error) {

View File

@ -1,9 +1,11 @@
package netdb
import (
"github.com/go-i2p/go-i2p/lib/common"
"github.com/go-i2p/go-i2p/lib/tunnel"
"time"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/go-i2p/go-i2p/lib/tunnel"
)
// resolves router infos with recursive kademlia lookup
@ -16,7 +18,7 @@ type kadResolver struct {
}
// TODO: implement
func (kr *kadResolver) Lookup(h common.Hash, timeout time.Duration) (chnl chan common.RouterInfo) {
func (kr *kadResolver) Lookup(h common.Hash, timeout time.Duration) (chnl chan router_info.RouterInfo) {
return
}

View File

@ -1,16 +1,18 @@
package netdb
import (
"github.com/go-i2p/go-i2p/lib/bootstrap"
"github.com/go-i2p/go-i2p/lib/common"
"time"
"github.com/go-i2p/go-i2p/lib/bootstrap"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/router_info"
)
// resolves unknown RouterInfos given the hash of their RouterIdentity
type Resolver interface {
// resolve a router info by hash
// return a chan that yields the found RouterInfo or nil if it could not be found after timeout
Lookup(hash common.Hash, timeout time.Duration) chan common.RouterInfo
Lookup(hash common.Hash, timeout time.Duration) chan router_info.RouterInfo
}
// i2p network database, storage of i2p RouterInfos
@ -18,10 +20,10 @@ type NetworkDatabase interface {
// obtain a RouterInfo by its hash locally
// return a RouterInfo if we found it locally
// return nil if the RouterInfo cannot be found locally
GetRouterInfo(hash common.Hash) common.RouterInfo
GetRouterInfo(hash common.Hash) router_info.RouterInfo
// store a router info locally
StoreRouterInfo(ri common.RouterInfo)
StoreRouterInfo(ri router_info.RouterInfo)
// try obtaining more peers with a bootstrap instance until we get minRouters number of router infos
// returns error if bootstrap.GetPeers returns an error otherwise returns nil

View File

@ -3,23 +3,25 @@ package netdb
import (
"bytes"
"fmt"
"github.com/go-i2p/go-i2p/lib/bootstrap"
"github.com/go-i2p/go-i2p/lib/common"
"github.com/go-i2p/go-i2p/lib/common/base64"
"github.com/go-i2p/go-i2p/lib/util"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/go-i2p/go-i2p/lib/bootstrap"
"github.com/go-i2p/go-i2p/lib/common/base64"
common "github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/go-i2p/go-i2p/lib/util"
log "github.com/sirupsen/logrus"
)
// standard network database implementation using local filesystem skiplist
type StdNetDB string
func (db StdNetDB) GetRouterInfo(hash common.Hash) (chnl chan common.RouterInfo) {
func (db StdNetDB) GetRouterInfo(hash common.Hash) (chnl chan router_info.RouterInfo) {
fname := db.SkiplistFile(hash)
f, err := os.Open(fname)
if err != nil {
@ -28,8 +30,11 @@ func (db StdNetDB) GetRouterInfo(hash common.Hash) (chnl chan common.RouterInfo)
buff := new(bytes.Buffer)
_, err = io.Copy(buff, f)
f.Close()
chnl = make(chan common.RouterInfo)
chnl <- common.RouterInfo(buff.Bytes())
chnl = make(chan router_info.RouterInfo)
ri, _, err := router_info.ReadRouterInfo(buff.Bytes())
if err == nil {
chnl <- ri
}
return
}
@ -45,9 +50,7 @@ func (db StdNetDB) Path() string {
return string(db)
}
//
// return how many routers we know about in our network database
//
func (db StdNetDB) Size() (routers int) {
// TODO: implement this
var err error
@ -113,7 +116,7 @@ func (db StdNetDB) Exists() bool {
_, err := os.Stat(p)
if err == nil {
// check subdirectories for skiplist
for _, c := range base64.Alphabet {
for _, c := range base64.I2PEncodeAlphabet {
if _, err = os.Stat(filepath.Join(p, fmt.Sprintf("r%c", c))); err != nil {
return false
}
@ -125,14 +128,14 @@ func (db StdNetDB) Exists() bool {
func (db StdNetDB) SaveEntry(e *Entry) (err error) {
var f io.WriteCloser
var h common.Hash
h, err = e.ri.IdentHash()
h = e.ri.IdentHash()
//if err == nil {
f, err = os.OpenFile(db.SkiplistFile(h), os.O_WRONLY|os.O_CREATE, 0700)
if err == nil {
f, err = os.OpenFile(db.SkiplistFile(h), os.O_WRONLY|os.O_CREATE, 0700)
if err == nil {
err = e.WriteTo(f)
f.Close()
}
err = e.WriteTo(f)
f.Close()
}
//}
if err != nil {
log.Errorf("failed to save netdb entry: %s", err.Error())
}
@ -163,7 +166,7 @@ func (db StdNetDB) Create() (err error) {
err = os.Mkdir(p, mode)
if err == nil {
// create all subdirectories for skiplist
for _, c := range base64.Alphabet {
for _, c := range base64.I2PEncodeAlphabet {
err = os.Mkdir(filepath.Join(p, fmt.Sprintf("r%c", c)), mode)
if err != nil {
return

589
lib/su3/su3.go Normal file
View File

@ -0,0 +1,589 @@
// Package su3 implements reading the SU3 file format.
//
// SU3 files provide content that is signed by a known identity.
// They are used to distribute many types of data, including reseed files,
// plugins, blocklists, and more.
//
// See: https://geti2p.net/spec/updates#su3-file-specification
//
// The Read() function takes an io.Reader, and it returns a *SU3. The *SU3 contains
// the SU3 file metadata, such as the type of the content and the signer ID.
// In order to get the file contents, one must pass in the public key associated
// with the file's signer, so that the signature can be validated. The content
// can still be read without passing in the key, but after returning the full
// content the error ErrInvalidSignature will be returned.
//
// Example usage:
//
// // Let's say we are reading an SU3 file from an HTTP body, which is an io.Reader.
// su3File, err := su3.Read(body)
// if err != nil {
// // Handle error.
// }
// // Look up this signer's key.
// key := somehow_lookup_the_key(su3File.SignerID)
// // Read the content.
// contentReader := su3File.Content(key)
// bytes, err := ioutil.ReadAll(contentReader)
// if errors.Is(err, su3.ErrInvalidSignature) {
// // The signature is invalid, OR a nil key was provided.
// } else if err != nil {
// // Handle error.
// }
//
// If you want to parse from a []byte, you can wrap it like this:
//
// mySU3FileBytes := []byte{0x00, 0x01, 0x02, 0x03}
// su3File, err := su3.Read(bytes.NewReader(mySU3FileBytes))
//
// One of the advantages of this library's design is that you can avoid buffering
// the file contents in memory. Here's how you would stream from an HTTP body
// directly to disk:
//
// su3File, err := su3.Read(body)
// if err != nil {
// // Handle error.
// }
// // Look up this signer's key.
// key := somehow_lookup_the_key(su3File.SignerID)
// // Stream directly to disk.
// f, err := os.Create("my_file.txt")
// if err != nil {
// // Handle error.
// }
// _, err := io.Copy(f, su3File.Content(key))
// if errors.Is(err, su3.ErrInvalidSignature) {
// // The signature is invalid, OR a nil key was provided.
// // Don't trust the file, delete it!
// } else if err != nil {
// // Handle error.
// }
//
// Note: if you want to read the content, the Content() io.Reader must be read
// *before* the Signature() io.Reader. If you read the signature first, the
// content bytes will be thrown away. If you then attempt to read the content,
// you will get an error. For clarification, see TestReadSignatureFirst.
package su3
import (
"bytes"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"errors"
"fmt"
"hash"
"io"
"io/ioutil"
"strings"
"sync"
)
type SignatureType string
const (
DSA_SHA1 SignatureType = "DSA-SHA1"
ECDSA_SHA256_P256 SignatureType = "ECDSA-SHA256-P256"
ECDSA_SHA384_P384 SignatureType = "ECDSA-SHA384-P384"
ECDSA_SHA512_P521 SignatureType = "ECDSA-SHA512-P521"
RSA_SHA256_2048 SignatureType = "RSA-SHA256-2048"
RSA_SHA384_3072 SignatureType = "RSA-SHA384-3072"
RSA_SHA512_4096 SignatureType = "RSA-SHA512-4096"
EdDSA_SHA512_Ed25519ph SignatureType = "EdDSA-SHA512-Ed25519ph"
)
var sigTypes = map[[2]byte]SignatureType{
{0x00, 0x00}: DSA_SHA1,
{0x00, 0x01}: ECDSA_SHA256_P256,
{0x00, 0x02}: ECDSA_SHA384_P384,
{0x00, 0x03}: ECDSA_SHA512_P521,
{0x00, 0x04}: RSA_SHA256_2048,
{0x00, 0x05}: RSA_SHA384_3072,
{0x00, 0x06}: RSA_SHA512_4096,
{0x00, 0x08}: EdDSA_SHA512_Ed25519ph,
}
type FileType string
const (
ZIP FileType = "zip"
XML FileType = "xml"
HTML FileType = "html"
XML_GZIP FileType = "xml.gz"
TXT_GZIP FileType = "txt.gz"
DMG FileType = "dmg"
EXE FileType = "exe"
)
var fileTypes = map[byte]FileType{
0x00: ZIP,
0x01: XML,
0x02: HTML,
0x03: XML_GZIP,
0x04: TXT_GZIP,
0x05: DMG,
0x06: EXE,
}
type ContentType string
const (
UNKNOWN ContentType = "unknown"
ROUTER_UPDATE ContentType = "router_update"
PLUGIN ContentType = "plugin"
RESEED ContentType = "reseed"
NEWS ContentType = "news"
BLOCKLIST ContentType = "blocklist"
)
var contentTypes = map[byte]ContentType{
0x00: UNKNOWN,
0x01: ROUTER_UPDATE,
0x02: PLUGIN,
0x03: RESEED,
0x04: NEWS,
0x05: BLOCKLIST,
}
var ErrMissingMagicBytes = errors.New("missing magic bytes")
var ErrMissingUnusedByte6 = errors.New("missing unused byte 6")
var ErrMissingFileFormatVersion = errors.New("missing or incorrect file format version")
var ErrMissingSignatureType = errors.New("missing or invalid signature type")
var ErrUnsupportedSignatureType = errors.New("unsupported signature type")
var ErrMissingSignatureLength = errors.New("missing signature length")
var ErrMissingUnusedByte12 = errors.New("missing unused byte 12")
var ErrMissingVersionLength = errors.New("missing version length")
var ErrVersionTooShort = errors.New("version length too short")
var ErrMissingUnusedByte14 = errors.New("missing unused byte 14")
var ErrMissingSignerIDLength = errors.New("missing signer ID length")
var ErrMissingContentLength = errors.New("missing content length")
var ErrMissingUnusedByte24 = errors.New("missing unused byte 24")
var ErrMissingFileType = errors.New("missing or invalid file type")
var ErrMissingUnusedByte26 = errors.New("missing unused byte 26")
var ErrMissingContentType = errors.New("missing or invalid content type")
var ErrMissingUnusedBytes28To39 = errors.New("missing unused bytes 28-39")
var ErrMissingVersion = errors.New("missing version")
var ErrMissingSignerID = errors.New("missing signer ID")
var ErrMissingContent = errors.New("missing content")
var ErrMissingSignature = errors.New("missing signature")
var ErrInvalidPublicKey = errors.New("invalid public key")
var ErrInvalidSignature = errors.New("invalid signature")
const magicBytes = "I2Psu3"
type SU3 struct {
SignatureType SignatureType
SignatureLength uint16
ContentLength uint64
FileType FileType
ContentType ContentType
Version string
SignerID string
mut sync.Mutex
reader io.Reader
publicKey interface{}
contentReader *contentReader
signatureReader *signatureReader
}
func (su3 *SU3) Content(publicKey interface{}) io.Reader {
su3.publicKey = publicKey
return su3.contentReader
}
func (su3 *SU3) Signature() io.Reader {
return su3.signatureReader
}
func Read(reader io.Reader) (su3 *SU3, err error) {
// We will buffer everything up to the content, so that once we know
// the hash type being used for the signature, we can write these bytes
// into the hash.
var buff bytes.Buffer
// Magic bytes.
mbytes := make([]byte, len(magicBytes))
l, err := reader.Read(mbytes)
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading magic bytes: %w", err)
}
if l != len(mbytes) {
return nil, ErrMissingMagicBytes
}
if string(mbytes) != magicBytes {
return nil, ErrMissingMagicBytes
}
buff.Write(mbytes)
// Unused byte 6.
unused := [1]byte{}
l, err = reader.Read(unused[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading unused byte 6: %w", err)
}
if l != 1 {
return nil, ErrMissingUnusedByte6
}
buff.Write(unused[:])
// SU3 file format version (always 0).
l, err = reader.Read(unused[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading SU3 file format version: %w", err)
}
if l != 1 {
return nil, ErrMissingFileFormatVersion
}
if unused[0] != 0x00 {
return nil, ErrMissingFileFormatVersion
}
buff.Write(unused[:])
su3 = &SU3{
mut: sync.Mutex{},
reader: reader,
}
// Signature type.
sigTypeBytes := [2]byte{}
l, err = reader.Read(sigTypeBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading signature type: %w", err)
}
if l != 2 {
return nil, ErrMissingSignatureType
}
sigType, ok := sigTypes[sigTypeBytes]
if !ok {
return nil, ErrUnsupportedSignatureType
}
su3.SignatureType = sigType
buff.Write(sigTypeBytes[:])
// Signature length.
sigLengthBytes := [2]byte{}
l, err = reader.Read(sigLengthBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading signature length: %w", err)
}
if l != 2 {
return nil, ErrMissingSignatureLength
}
sigLen := binary.BigEndian.Uint16(sigLengthBytes[:])
// TODO check that sigLen is the correct length for sigType.
su3.SignatureLength = sigLen
buff.Write(sigLengthBytes[:])
// Unused byte 12.
l, err = reader.Read(unused[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading unused byte 12: %w", err)
}
if l != 1 {
return nil, ErrMissingUnusedByte12
}
buff.Write(unused[:])
// Version length.
verLengthBytes := [1]byte{}
l, err = reader.Read(verLengthBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading version length: %w", err)
}
if l != 1 {
return nil, ErrMissingVersionLength
}
verLen := binary.BigEndian.Uint16([]byte{0x00, verLengthBytes[0]})
if verLen < 16 {
return nil, ErrVersionTooShort
}
buff.Write(verLengthBytes[:])
// Unused byte 14.
l, err = reader.Read(unused[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading unused byte 14: %w", err)
}
if l != 1 {
return nil, ErrMissingUnusedByte14
}
buff.Write(unused[:])
// Signer ID length.
sigIDLengthBytes := [1]byte{}
l, err = reader.Read(sigIDLengthBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading signer id length: %w", err)
}
if l != 1 {
return nil, ErrMissingSignerIDLength
}
signIDLen := binary.BigEndian.Uint16([]byte{0x00, sigIDLengthBytes[0]})
buff.Write(sigIDLengthBytes[:])
// Content length.
contentLengthBytes := [8]byte{}
l, err = reader.Read(contentLengthBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading content length: %w", err)
}
if l != 8 {
return nil, ErrMissingContentLength
}
contentLen := binary.BigEndian.Uint64(contentLengthBytes[:])
su3.ContentLength = contentLen
buff.Write(contentLengthBytes[:])
// Unused byte 24.
l, err = reader.Read(unused[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading unused byte 24: %w", err)
}
if l != 1 {
return nil, ErrMissingUnusedByte24
}
buff.Write(unused[:])
// File type.
fileTypeBytes := [1]byte{}
l, err = reader.Read(fileTypeBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading file type: %w", err)
}
if l != 1 {
return nil, ErrMissingFileType
}
fileType, ok := fileTypes[fileTypeBytes[0]]
if !ok {
return nil, ErrMissingFileType
}
su3.FileType = fileType
buff.Write(fileTypeBytes[:])
// Unused byte 26.
l, err = reader.Read(unused[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading unused byte 26: %w", err)
}
if l != 1 {
return nil, ErrMissingUnusedByte26
}
buff.Write(unused[:])
// Content type.
contentTypeBytes := [1]byte{}
l, err = reader.Read(contentTypeBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading content type: %w", err)
}
if l != 1 {
return nil, ErrMissingContentType
}
contentType, ok := contentTypes[contentTypeBytes[0]]
if !ok {
return nil, ErrMissingContentType
}
su3.ContentType = contentType
buff.Write(contentTypeBytes[:])
// Unused bytes 28-39.
for i := 0; i < 12; i++ {
l, err = reader.Read(unused[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading unused bytes 28-39: %w", err)
}
if l != 1 {
return nil, ErrMissingUnusedBytes28To39
}
buff.Write(unused[:])
}
// Version.
versionBytes := make([]byte, verLen)
l, err = reader.Read(versionBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading version: %w", err)
}
if l != int(verLen) {
return nil, ErrMissingVersion
}
version := strings.TrimRight(string(versionBytes), "\x00")
su3.Version = version
buff.Write(versionBytes[:])
// Signer ID.
signerIDBytes := make([]byte, signIDLen)
l, err = reader.Read(signerIDBytes[:])
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("reading signer id: %w", err)
}
if l != int(signIDLen) {
return nil, ErrMissingSignerID
}
signerID := string(signerIDBytes)
su3.SignerID = signerID
buff.Write(signerIDBytes[:])
su3.contentReader = &contentReader{
su3: su3,
}
switch sigType {
case RSA_SHA256_2048:
su3.contentReader.hash = sha256.New()
case RSA_SHA512_4096:
su3.contentReader.hash = sha512.New()
}
if su3.contentReader.hash != nil {
su3.contentReader.hash.Write(buff.Bytes())
}
su3.signatureReader = &signatureReader{
su3: su3,
}
return su3, nil
}
type fixedLengthReader struct {
length uint64
readSoFar uint64
reader io.Reader
}
func (r *fixedLengthReader) Read(p []byte) (n int, err error) {
if r.readSoFar >= r.length {
return 0, io.EOF
}
if uint64(len(p)) > r.length-r.readSoFar {
p = p[:r.length-r.readSoFar]
}
n, err = r.reader.Read(p)
r.readSoFar += uint64(n)
return n, err
}
type contentReader struct {
su3 *SU3
reader *fixedLengthReader
hash hash.Hash
finished bool
}
func (r *contentReader) Read(p []byte) (n int, err error) {
r.su3.mut.Lock()
defer r.su3.mut.Unlock()
if r.finished {
return 0, errors.New("out of bytes, maybe you read the signature before you read the content")
}
if r.reader == nil {
r.reader = &fixedLengthReader{
length: r.su3.ContentLength,
readSoFar: 0,
reader: r.su3.reader,
}
}
l, err := r.reader.Read(p)
if err != nil && !errors.Is(err, io.EOF) {
return l, fmt.Errorf("reading content: %w", err)
} else if errors.Is(err, io.EOF) && r.reader.readSoFar != r.su3.ContentLength {
return l, ErrMissingContent
} else if errors.Is(err, io.EOF) {
r.finished = true
}
if r.hash != nil {
r.hash.Write(p[:l])
}
if r.finished {
if r.su3.publicKey == nil {
return l, ErrInvalidSignature
}
r.su3.signatureReader.getBytes()
if r.su3.signatureReader.err != nil {
return l, r.su3.signatureReader.err
}
// TODO support all signature types
switch r.su3.SignatureType {
case RSA_SHA256_2048:
var pubKey *rsa.PublicKey
if k, ok := r.su3.publicKey.(*rsa.PublicKey); !ok {
return l, ErrInvalidPublicKey
} else {
pubKey = k
}
err := rsa.VerifyPKCS1v15(pubKey, 0, r.hash.Sum(nil), r.su3.signatureReader.bytes)
if err != nil {
return l, ErrInvalidSignature
}
case RSA_SHA512_4096:
var pubKey *rsa.PublicKey
if k, ok := r.su3.publicKey.(*rsa.PublicKey); !ok {
return l, ErrInvalidPublicKey
} else {
pubKey = k
}
err := rsa.VerifyPKCS1v15(pubKey, 0, r.hash.Sum(nil), r.su3.signatureReader.bytes)
if err != nil {
return l, ErrInvalidSignature
}
default:
return l, ErrUnsupportedSignatureType
}
}
return l, err
}
type signatureReader struct {
su3 *SU3
bytes []byte
err error
reader io.Reader
}
func (r *signatureReader) getBytes() {
// If content hasn't been read yet, throw it away.
if !r.su3.contentReader.finished {
_, err := ioutil.ReadAll(r.su3.contentReader)
if err != nil {
r.err = fmt.Errorf("reading content: %w", err)
return
}
}
// Read signature.
reader := &fixedLengthReader{
length: uint64(r.su3.SignatureLength),
readSoFar: 0,
reader: r.su3.reader,
}
sigBytes, err := ioutil.ReadAll(reader)
if err != nil {
r.err = fmt.Errorf("reading signature: %w", err)
} else if reader.readSoFar != uint64(r.su3.SignatureLength) {
r.err = ErrMissingSignature
} else {
r.bytes = sigBytes
r.reader = bytes.NewReader(sigBytes)
}
}
func (r *signatureReader) Read(p []byte) (n int, err error) {
r.su3.mut.Lock()
defer r.su3.mut.Unlock()
if len(r.bytes) == 0 {
r.getBytes()
}
if r.err != nil {
return 0, r.err
}
return r.reader.Read(p)
}

415
lib/su3/su3_test.go Normal file
View File

@ -0,0 +1,415 @@
package su3
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/binary"
"encoding/pem"
"fmt"
"github.com/stretchr/testify/assert"
"io"
"io/ioutil"
"os"
"testing"
)
func fileReader(t *testing.T, filename string) io.Reader {
f, err := os.Open(filename)
if err != nil {
t.Fatalf("cannot read test data file %s: %s", filename, err)
}
return f
}
func fileBytes(t *testing.T, filename string) []byte {
b, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("cannot read test data file %s: %s", filename, err)
}
return b
}
func appendBytes(b ...[]byte) []byte {
var out []byte
for _, bb := range b {
out = append(out, bb...)
}
return out
}
func fileRSAPubKey(t *testing.T, filename string) *rsa.PublicKey {
b := fileBytes(t, filename)
block, rest := pem.Decode(b)
if len(rest) > 0 {
t.Fatalf("cannot decode PEM block %s: %d bytes left over", filename, len(rest))
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("cannot parse certificate file %s: %s", filename, err)
}
var pubKey *rsa.PublicKey
if k, ok := cert.PublicKey.(*rsa.PublicKey); !ok {
t.Fatalf("expected rsa.PublicKey from file %s", filename)
} else {
pubKey = k
}
return pubKey
}
// This fake data is generated in TestMain.
var aliceFakeKey *rsa.PrivateKey
var bobFakeKey *rsa.PrivateKey
var aliceContent []byte
var aliceSignature []byte
var aliceSU3 []byte
func TestRead(t *testing.T) {
tests := []struct {
name string
skip bool
reader io.Reader
key interface{}
wantErr string
wantSU3 *SU3
wantContent []byte
wantSignature []byte
}{
{
name: "zero_bytes",
reader: bytes.NewReader([]byte{}),
wantErr: ErrMissingMagicBytes.Error(),
},
{
name: "magic_bytes_not_long_enough",
reader: bytes.NewReader(aliceSU3[:3]),
wantErr: ErrMissingMagicBytes.Error(),
},
{
name: "magic_bytes_incorrect",
reader: bytes.NewReader([]byte("XXXXXX")),
wantErr: ErrMissingMagicBytes.Error(),
},
{
name: "missing_unused_byte_6",
reader: bytes.NewReader(aliceSU3[:6]),
wantErr: ErrMissingUnusedByte6.Error(),
},
{
name: "missing_file_format",
reader: bytes.NewReader(aliceSU3[:7]),
wantErr: ErrMissingFileFormatVersion.Error(),
},
{
name: "incorrect_file_format",
reader: bytes.NewReader(appendBytes(
aliceSU3[:7],
[]byte{0x01}, // Incorrect file format
)),
wantErr: ErrMissingFileFormatVersion.Error(),
},
{
name: "missing_signature_type",
reader: bytes.NewReader(aliceSU3[:8]),
wantErr: ErrMissingSignatureType.Error(),
},
{
name: "unsupported_signature_type",
reader: bytes.NewReader(appendBytes(
aliceSU3[:8],
[]byte{0x99, 0x99}, // Unsupported signature type
)),
wantErr: ErrUnsupportedSignatureType.Error(),
},
{
name: "missing_signature_length",
reader: bytes.NewReader(aliceSU3[:10]),
wantErr: ErrMissingSignatureLength.Error(),
},
{
name: "missing_unused_byte_12",
reader: bytes.NewReader(aliceSU3[:12]),
wantErr: ErrMissingUnusedByte12.Error(),
},
{
name: "missing_version_length",
reader: bytes.NewReader(aliceSU3[:13]),
wantErr: ErrMissingVersionLength.Error(),
},
{
name: "version_too_short",
reader: bytes.NewReader(appendBytes(
aliceSU3[:13],
[]byte{0x01}, // Version length 1
)),
wantErr: ErrVersionTooShort.Error(),
},
{
name: "missing_unused_byte_14",
reader: bytes.NewReader(aliceSU3[:14]),
wantErr: ErrMissingUnusedByte14.Error(),
},
{
name: "missing_signer_length",
reader: bytes.NewReader(aliceSU3[:15]),
wantErr: ErrMissingSignerIDLength.Error(),
},
{
name: "missing_content_length",
reader: bytes.NewReader(aliceSU3[:16]),
wantErr: ErrMissingContentLength.Error(),
},
{
name: "missing_unused_byte_24",
reader: bytes.NewReader(aliceSU3[:24]),
wantErr: ErrMissingUnusedByte24.Error(),
},
{
name: "missing_file_type",
reader: bytes.NewReader(aliceSU3[:25]),
wantErr: ErrMissingFileType.Error(),
},
{
name: "invalid_file_type",
reader: bytes.NewReader(appendBytes(
aliceSU3[:25],
[]byte{0x99}, // Invalid file type
)),
wantErr: ErrMissingFileType.Error(),
},
{
name: "missing_unused_byte_26",
reader: bytes.NewReader(aliceSU3[:26]),
wantErr: ErrMissingUnusedByte26.Error(),
},
{
name: "missing_content_type",
reader: bytes.NewReader(aliceSU3[:27]),
wantErr: ErrMissingContentType.Error(),
},
{
name: "invalid_content_type",
reader: bytes.NewReader(appendBytes(
aliceSU3[:27],
[]byte{0x99}, // Invalid content type
)),
wantErr: ErrMissingContentType.Error(),
},
{
name: "missing_unused_bytes_28-39",
reader: bytes.NewReader(aliceSU3[:28]),
wantErr: ErrMissingUnusedBytes28To39.Error(),
},
{
name: "partial_unused_bytes_28-39",
reader: bytes.NewReader(appendBytes(
aliceSU3[:28],
[]byte{0x00, 0x00}, // Partial unused bytes 28-39
)),
wantErr: ErrMissingUnusedBytes28To39.Error(),
},
{
name: "missing_version",
reader: bytes.NewReader(aliceSU3[:40]),
wantErr: ErrMissingVersion.Error(),
},
{
name: "missing_signer_ID",
reader: bytes.NewReader(aliceSU3[:56]),
wantErr: ErrMissingSignerID.Error(),
},
{
name: "missing_content",
reader: bytes.NewReader(aliceSU3[:61]),
wantErr: ErrMissingContent.Error(),
},
{
name: "missing_signature",
reader: bytes.NewReader(aliceSU3[:72]),
key: &aliceFakeKey.PublicKey,
wantErr: ErrMissingSignature.Error(),
},
{
name: "invalid_signature",
reader: bytes.NewReader(aliceSU3),
key: &bobFakeKey.PublicKey,
wantErr: ErrInvalidSignature.Error(),
},
{
name: "complete_with_valid_signature",
reader: bytes.NewReader(aliceSU3),
key: &aliceFakeKey.PublicKey,
wantSU3: &SU3{
SignatureType: RSA_SHA256_2048,
SignatureLength: uint16(len(aliceSignature)),
ContentLength: uint64(len(aliceContent)),
FileType: HTML,
ContentType: UNKNOWN,
Version: "1234567890",
SignerID: "alice",
},
wantContent: aliceContent,
wantSignature: aliceSignature,
},
{
// Skipping this for now, as the signature doesn't seem to match.
name: "reseed-i2pgit.su3",
reader: fileReader(t, "testdata/reseed-i2pgit.su3"),
key: fileRSAPubKey(t, "./testdata/reseed-hankhill19580_at_gmail.com.crt"),
wantSU3: &SU3{
SignatureType: RSA_SHA512_4096,
SignatureLength: 512,
ContentLength: 80138,
FileType: ZIP,
ContentType: RESEED,
Version: "1658849028",
SignerID: "hankhill19580@gmail.com",
},
wantContent: fileBytes(t, "testdata/reseed-i2pgit-content.zip"),
wantSignature: fileBytes(t, "testdata/reseed-i2pgit-signature"),
},
{
// Skipping this for now, as the signature doesn't seem to match.
name: "snowflake-linux.su3",
reader: fileReader(t, "testdata/snowflake-linux.su3"),
key: fileRSAPubKey(t, "./testdata/snowflake-hankhill19580_at_gmail.com.crt"),
wantSU3: &SU3{
SignatureType: RSA_SHA512_4096,
SignatureLength: 512,
ContentLength: 4511938,
FileType: ZIP,
ContentType: PLUGIN,
Version: "0.0.47",
SignerID: "hankhill19580@gmail.com",
},
wantContent: fileBytes(t, "testdata/snowflake-content"),
wantSignature: fileBytes(t, "testdata/snowflake-signature"),
},
{
// Skipping this for now, as the signature doesn't seem to match.
name: "novg.su3",
reader: fileReader(t, "testdata/novg.su3"),
key: fileRSAPubKey(t, "./testdata/igor_at_novg.net.crt"),
wantSU3: &SU3{
SignatureType: RSA_SHA512_4096,
SignatureLength: 512,
ContentLength: 81367,
FileType: ZIP,
ContentType: RESEED,
Version: "1659048682",
SignerID: "igor@novg.net",
},
wantContent: fileBytes(t, "testdata/novg-content.zip"),
wantSignature: fileBytes(t, "testdata/novg-signature"),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
if test.skip {
t.Skip()
}
su3, err := Read(test.reader)
var content, signature []byte
if err == nil {
content, err = ioutil.ReadAll(su3.Content(test.key))
if err == nil {
signature, err = ioutil.ReadAll(su3.Signature())
}
}
if test.wantErr != "" && err == nil {
t.Fatal("expected error, got nil")
} else if test.wantErr != "" {
assert.Contains(t, err.Error(), test.wantErr, fmt.Sprintf("expected error to contain `%s`", test.wantErr))
} else if err != nil {
assert.Nil(t, err, "expected nil error")
} else {
assert.Equal(t, test.wantSU3.SignatureType, su3.SignatureType, "expected SignatureType to match")
assert.Equal(t, test.wantSU3.SignatureLength, su3.SignatureLength, "expected SignatureLength to match")
assert.Equal(t, test.wantSU3.ContentLength, su3.ContentLength, "expected ContentLength to match")
assert.Equal(t, test.wantSU3.FileType, su3.FileType, "expected FileType to match")
assert.Equal(t, test.wantSU3.ContentType, su3.ContentType, "expected ContentType to match")
assert.Equal(t, test.wantSU3.Version, su3.Version, "expected Version to match")
assert.Equal(t, test.wantSU3.SignerID, su3.SignerID, "expected SignerID to match")
assert.Equal(t, test.wantContent, content, "expected content to match")
assert.Equal(t, test.wantSignature, signature, "expected signature to match")
}
})
}
}
func TestReadSignatureFirst(t *testing.T) {
// Skipping this for now, since the signature doesn't seem to match.
t.Skip()
assert := assert.New(t)
reader := fileReader(t, "testdata/reseed-i2pgit.su3")
su3, err := Read(reader)
assert.Nil(err)
// Read only the signature.
sig, err := ioutil.ReadAll(su3.Signature())
assert.Nil(err)
assert.Equal(fileBytes(t, "testdata/reseed-i2pgit-signature"), sig)
// Reading content should give an error.
_, err = ioutil.ReadAll(su3.Content(nil))
assert.NotNil(err)
}
func TestMain(m *testing.M) {
// Generate fake RSA keys for test data.
var err error
aliceFakeKey, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
bobFakeKey, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// Generate fake SU3 file bytes.
aliceContent = []byte("alice rules")
contentLength := make([]byte, 8)
binary.BigEndian.PutUint64(contentLength, uint64(len(aliceContent)))
signatureLength := make([]byte, 2)
binary.BigEndian.PutUint16(signatureLength, uint16(256))
aliceSU3 = appendBytes(
[]byte("I2Psu3"), // Magic bytes
[]byte{0x00}, // Unused byte 6
[]byte{0x00}, // File format
[]byte{0x00, 0x04}, // Signature type RSA_SHA256_2048
signatureLength, // Signature length
[]byte{0x00}, // Unused byte 12
[]byte{0x10}, // Version length 16
[]byte{0x00}, // Unused byte 14
[]byte{0x05}, // Signer ID length 5
contentLength, // Content length
[]byte{0x00}, // Unused byte 24
[]byte{0x02}, // File type HTML
[]byte{0x00}, // Unused byte 26
[]byte{0x00}, // Content type unknown
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Unused bytes 28-39
appendBytes([]byte("1234567890"), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), // Version with padding
[]byte("alice"), // Signer ID
aliceContent, // Content
)
hash := sha256.New()
_, err = hash.Write(aliceSU3)
if err != nil {
panic(err)
}
sum := hash.Sum(nil)
aliceSignature, err = rsa.SignPKCS1v15(rand.Reader, aliceFakeKey, 0, sum)
if err != nil {
panic(err)
}
aliceSU3 = appendBytes(aliceSU3, aliceSignature)
// Run tests.
os.Exit(m.Run())
}

16
lib/su3/testdata/README.md vendored Normal file
View File

@ -0,0 +1,16 @@
This directory contains test data for the `su3` package.
- `reseed-hankhill19580_at_gmail.com.crt` - A 4096-bit RSA public key used to sign `reseed-i2pgit.su3`.
- `reseed-i2pgit.su3` - A reseed file obtained from [https://reseed.i2pgit.org/](https://reseed.i2pgit.org/) on 2022-07-28.
- `reseed-i2pgit-content.zip` - The content of the above reseed file.
- `reseed-i2pgit-signature` - The signature of the above reseed file.
- `snowflake-hankhill19580_at_gmail.com.crt` - A 4096-bit RSA public key used to sign `snowflake-linux.su3`.
- `snowflake-linux.su3` - A plugin file obtained from [https://github.com/eyedeekay/blizzard/releases](https://github.com/eyedeekay/blizzard/releases) on 2022-08-02.
- `snowflake-content` - The content of the above plugin file.
- `snowflake-signature` - The signature of the above plugin file.
- `igor_at_novg.net.crt` - A 4096-bit RSA public key used to sign `novg.su3`.
- `novg.su3` - A reseed file obtained from [https://i2p.novg.net/](https://i2p.novg.net/) on 2022-08-02.
- `novg-content.zip` - The content of the above reseed file.
- `novg-signature` - The signature of the above reseed file.

33
lib/su3/testdata/igor_at_novg.net.crt vendored Normal file
View File

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFvjCCA6agAwIBAgIQIDtv8tGMh0FyB2w5XjfZxTANBgkqhkiG9w0BAQsFADBt
MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK
ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwN
aWdvckBub3ZnLm5ldDAeFw0xNzA3MjQxODI4NThaFw0yNzA3MjQxODI4NThaMG0x
CzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNVBAoT
FUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1p
Z29yQG5vdmcubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxst4
cam3YibBtQHGPCPX13uRQti56U3XZytSZntaKrUFmJxjt41Q/mOy3KYo+lBvhfDF
x3tWKjgP9LJOJ28zvddFhZVNxqZRjcnAoPuSOVCw88g01D9OAasKF11hCfdxZP6h
vGm8WCnjD8KPcYFxJC4HJUiFeProAwuTzEAESTRk4CAQe3Ie91JspuqoLUc5Qxlm
w5QpjnjfZY4kaVHmZDKGIZDgNIt5v85bu4pWwZ6O+o90xQqjxvjyz/xccIec3sHw
MHJ8h8ZKMokCKEJTaRWBvdeNXki7nf3gUy/3GjYQlzo0Nxk/Hw4svPcA+eL0AYiy
Jn83bIB5VToW2zYUdV4u3qHeAhEg8Y7HI0kKcSUGm9AQXzbzP8YCHxi0sbb0GAJy
f1Xf3XzoPfT64giD8ReUHhwKpyMB6uvG/NfWSZAzeAO/NT7DAwXpKIVQdkVdqy8b
mvHvjf9/kWKOirA2Nygf3r79Vbg2mqbYC/b63XI9hheU689+O7qyhTEhNz+11X0d
Zax7UPrLrwOeB9TNfEnztsmrHNdv2n+KcOO2o11Wvz2nHP9g+dgwoZSD1ZEpFzWP
0sD5knKLwAL/64qLlAQ1feqW7hMr80IADcKjLSODkIDIIGm0ksXqEzTjz1JzbRDq
jUjq7EAlkw3G69rv1gHxIntllJRQidAqecyWHOMCAwEAAaNaMFgwDgYDVR0PAQH/
BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8E
BTADAQH/MBYGA1UdDgQPBA1pZ29yQG5vdmcubmV0MA0GCSqGSIb3DQEBCwUAA4IC
AQADyPaec28qc1HQtAV5dscJr47k92RTfvan+GEgIwyQDHZQm38eyTb05xipQCdk
5ruUDFXLB5qXXFJKUbQM6IpaktmWDJqk4Zn+1nGbtFEbKgrF55pd63+NQer5QW9o
3+dGj0eZJa3HX5EBkd2r7j2LFuB6uxv3r/xiTeHaaflCnsmyDLfb7axvYhyEzHQS
AUi1bR+ln+dXewdtuojqc1+YmVGDgzWZK2T0oOz2E21CpZUDiP3wv9QfMaotLEal
zECnbhS++q889inN3GB4kIoN6WpPpeYtTV+/r7FLv9+KUOV1s2z6mxIqC5wBFhZs
0Sr1kVo8hB/EW/YYhDp99LoAOjIO6nn1h+qttfzBYr6C16j+8lGK2A12REJ4LiUQ
cQI/0zTjt2C8Ns6ueNzMLQN1Mvmlg1Z8wIB7Az7jsIbY2zFJ0M5qR5VJveTj33K4
4WSbC/zMWOBYHTVBvGmc6JGhu5ZUTZ+mWP7QfimGu+tdhvtrybFjE9ROIE/4yFr6
GkxEyt0UY87TeKXJ/3KygvkMwdvqGWiZhItb807iy99+cySujtbGfF2ZXYGjBXVW
dJOVRbyGQkHh6lrWHQM4ntBv4x+5QA+OAan5PBF3tcDx1vefPx+asYslbOXpzII5
qhvoQxuRs6j5jsVFG6RdsKNeQAt87Mb2u2zK2ZakMdyD1w==
-----END CERTIFICATE-----

BIN
lib/su3/testdata/novg-content.zip vendored Normal file

Binary file not shown.

BIN
lib/su3/testdata/novg-signature vendored Normal file

Binary file not shown.

BIN
lib/su3/testdata/novg.su3 vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIRAKye34BRrKyQN6kMVPHddykwDQYJKoZIhvcNAQELBQAw
dzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxIDAeBgNVBAMM
F2hhbmtoaWxsMTk1ODBAZ21haWwuY29tMB4XDTIwMDUwNzA1MDkxMFoXDTMwMDUw
NzA1MDkxMFowdzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJY
WDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAx
IDAeBgNVBAMMF2hhbmtoaWxsMTk1ODBAZ21haWwuY29tMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA5Vt7c0SeUdVkcXXEYe3M9LmCTUyiCv/PHF2Puys6
8luLH8lO0U/pQ4j703kFKK7s4rV65jVpGNncjHWbfSCNevvs6VcbAFoo7oJX7Yjt
5+Z4oU1g7JG86feTwU6pzfFjAs0RO2lNq2L8AyLYKWOnPsVrmuGYl2c6N5WDzTxA
Et66IudfGsppTv7oZkgX6VNUMioV8tCjBTLaPCkSfyYKBX7r6ByHY86PflhFgYES
zIB92Ma75YFtCB0ktCM+o6d7wmnt10Iy4I6craZ+z7szCDRF73jhf3Vk7vGzb2cN
aCfr2riwlRJBaKrLJP5m0dGf5RdhviMgxc6JAgkN7Ius5lkxO/p3OSy5co0DrMJ7
lvwdZ2hu0dnO75unTt6ImR4RQ90Sqj7MUdorKR/8FcYEo+twBV8cV3s9kjuO5jxV
g976Q+GD3zDoixiege3W5UT4ff/Anm4mJpE5PKbNuO+KUjk6WA4B1PeudkEcxkO4
tQYy0aBzfjeyENee9otd4TgN1epY4wlHIORCa3HUFmFZd9VZMQcxwv7c47wl2kc9
Cv1L6Nae78wRzRu2CHD8zWhq+tv5q7Md2eRd3mFPI09ljsOgG2TQv6300WvHvI5M
enNdjYjLqOTRCzUJ2Jst4BZsvDxjWYkHsSZc1UORzm2LQmh2bJvbhC3m81qANGw6
ZhcCAwEAAaNkMGIwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMC
BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCAGA1UdDgQZBBdoYW5raGlsbDE5
NTgwQGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAVtMF7lrgkDLTNXlavI7h
HJqFxFHjmxPk3iu2Qrgwk302Gowqg5NjVVamT20cXeuJaUa6maTTHzDyyCai3+3e
roaosGxZQRpRf5/RBz2yhdEPLZBV9IqxGgIxvCWNqNIYB1SNk00rwC4q5heW1me0
EsOK4Mw5IbS2jUjbi9E5th781QDj91elwltghxwtDvpE2vzAJwmxwwBhjySGsKfq
w8SBZOxN+Ih5/IIpDnYGNoN1LSkJnBVGSkjY6OpstuJRIPYWl5zX5tJtYdaxiD+8
qNbFHBIZ5WrktMopJ3QJJxHdERyK6BFYYSzX/a1gO7woOFCkx8qMCsVzfcE/z1pp
JxJvshT32hnrKZ6MbZMd9JpTFclQ62RV5tNs3FPP3sbDsFtKBUtj87SW7XsimHbZ
OrWlPacSnQDbOoV5TfDDCqWi4PW2EqzDsDcg+Lc8EnBRIquWcAox2+4zmcQI29wO
C1TUpMT5o/wGyL/i9pf6GuTbH0D+aYukULropgSrK57EALbuvqnN3vh5l2QlX/rM
+7lCKsGCNLiJFXb0m6l/B9CC1947XVEbpMEAC/80Shwxl/UB+mKFpJxcNLFtPXzv
FYv2ixarBPbJx/FclOO8G91QC4ZhAKbsVZn5HPMSgtZe+xWM1r0/UJVChsMTafpd
CCOJyu3XtyzFf+tAeixOnuQ=
-----END CERTIFICATE-----

Binary file not shown.

BIN
lib/su3/testdata/reseed-i2pgit-signature vendored Normal file

Binary file not shown.

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