Compare commits
39 Commits
reseed-too
...
structify
Author | SHA1 | Date | |
---|---|---|---|
836f287c9d | |||
d9543745a4 | |||
a87847ef51 | |||
bffc1dfe38 | |||
c181a974cd | |||
27547611ed | |||
3f4d02dc3e | |||
6431100245 | |||
e6b02ab341 | |||
53b629dd15 | |||
a576f0685f | |||
fc0404a11a | |||
cfc3cc97ca | |||
a3b83b5e1e | |||
4a9943de9b | |||
82ba9c1b68 | |||
4f574a28d3 | |||
9d248eda5a | |||
4f19c48da3 | |||
961dfe4266 | |||
afb38b6165 | |||
0aa32b4aad | |||
3adf694c25 | |||
a11c3b73cb | |||
853bc79f8f | |||
77f1c6dd0a | |||
ee7d8a0d63 | |||
c253bf31ac | |||
b97b2854c1 | |||
d5266f8980 | |||
6de4dde1f2 | |||
648c05b15f | |||
bfc7237ba6 | |||
ffbdc7f967 | |||
1cd9d16760 | |||
896df4e483 | |||
49d7eeb441 | |||
7893694c91 | |||
1ea426da9c |
@ -1,26 +0,0 @@
|
||||
# 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
3
.gitignore
vendored
@ -5,5 +5,4 @@
|
||||
*.coverprofile
|
||||
*exportable-fuzz.zip
|
||||
go-i2p
|
||||
*.exe
|
||||
.idea/
|
||||
*.exe*.log
|
||||
|
9
.vscode/launch.json
vendored
Normal file
9
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
]
|
||||
}
|
@ -9,6 +9,7 @@ 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
|
||||
|
17
Makefile
17
Makefile
@ -1,8 +1,6 @@
|
||||
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
|
||||
@ -20,8 +18,8 @@ build: clean $(EXE)
|
||||
$(EXE):
|
||||
$(GO) build -v -o $(EXE)
|
||||
|
||||
test: fmt
|
||||
$(GO) test -vv -failfast ./lib/common/...
|
||||
test:
|
||||
$(GO) test -failfast ./...
|
||||
|
||||
clean:
|
||||
$(GO) clean -v
|
||||
@ -29,10 +27,5 @@ clean:
|
||||
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
|
||||
testcommon:
|
||||
$(GO) test -failfast ./lib/common/...
|
18
PASTA.md
18
PASTA.md
@ -1,18 +0,0 @@
|
||||
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.
|
122
README.md
122
README.md
@ -4,20 +4,35 @@ A pure Go implementation of the I2P router.
|
||||
|
||||
## Status
|
||||
|
||||
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.
|
||||
go-i2p was in early development. Now it's being restructured in some
|
||||
fundamental ways, so it's even less done than before(on this branch, for now)
|
||||
but when this restructuring is complete, it will be a fully-fledged I2P router
|
||||
and library for writing, embedding, and possiblly extending I2P routers in Go
|
||||
applications.
|
||||
|
||||
The go module is declared as: `github.com/go-i2p/go-i2p`, in order to clone
|
||||
anonymously you may use `torsocks` with `go get`(YMMV) or you may clone
|
||||
it from git.idk.i2p using:
|
||||
|
||||
#Set your $GOPATH, if it isn't set already then GOPATH=$HOME/go
|
||||
$GOPATH/go/src/i2pgit.org/idk/
|
||||
git clone git@127.0.0.1:idk/go-i2p $GOPATH/go/src/github.com/go-i2p/go-i2p
|
||||
$GOPATH/go/src/github.com/go-i2p/go-i2p
|
||||
|
||||
And build with `GO111MODULES=off` or use a `replace` directive in your `go.mod`
|
||||
to direct to the local module source. Or you may run your own Go Modules proxy as
|
||||
a hidden service. I'll make this about a billion times easier in the near future I
|
||||
promise.
|
||||
|
||||
### Implemented Features
|
||||
|
||||
- Clients
|
||||
- [ ] Datagrams
|
||||
- [ ] I2CP
|
||||
- [ ] Message routing
|
||||
- [ ] SAM
|
||||
- [ ] Streaming
|
||||
- [ ] Tunnel Manager
|
||||
As the application is restructured and moved away from representing I2P data
|
||||
structures as byte slices, this chart will be filled in, when the tests pass,
|
||||
the item will be checked off. Currently, much of this is partially implemented
|
||||
in byte-slice versions and partially implemented as Go Structs. Very little of
|
||||
it will work until it's all moved to Go Structs where appropriate. Most of
|
||||
this will happen in /lib/common.
|
||||
|
||||
- Cryptographic primitives
|
||||
- Signing
|
||||
- [ ] ECDSA_SHA256_P256
|
||||
@ -33,65 +48,62 @@ please keep up with these changes, as they will not be backward compatible and r
|
||||
- [ ] 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
|
||||
- [x] ElGamal
|
||||
- [x] AES256
|
||||
- Common Structures
|
||||
- Common Type Specification
|
||||
- [x] Integer
|
||||
- [x] Date
|
||||
- [x] String
|
||||
- [x] PublicKey* As interface in lib/crypto
|
||||
- [x] PrivateKey* As interface in lib/crypto
|
||||
- [ ] SessionKey
|
||||
- [ ] SigningPublicKey
|
||||
- [ ] Signature
|
||||
- [x] Hash
|
||||
- [ ] Session Tag
|
||||
- [ ] Tunnel ID
|
||||
- [x] Certificate
|
||||
- [ ] Mapping
|
||||
- Common Structure Specification
|
||||
- [ ] KeysAndCert
|
||||
- [ ] RouterIdentity
|
||||
- [ ] Destination
|
||||
- [ ] Lease
|
||||
- [ ] LeaseSet
|
||||
- [ ] Lease2
|
||||
- [ ] OfflineSigntature
|
||||
- [ ] LeaseSet2Header
|
||||
- [ ] LeaseSet2
|
||||
- [ ] MetaLease
|
||||
- [ ] MetaLeaseSet
|
||||
- [ ] EncryptedLeaseSet
|
||||
- [ ] RouterAddress
|
||||
- [ ] RouterInfo
|
||||
- I2NP
|
||||
- [ ] Message parsing
|
||||
- [ ] Message handling
|
||||
- NetDB
|
||||
- [ ] Local storage
|
||||
- [/] Persistence to disk
|
||||
- [X] Reseeding
|
||||
- [ ] Persistence to disk
|
||||
- [ ] Reseeding
|
||||
- [ ] Lookups
|
||||
- [ ] Expiry
|
||||
- [ ] Exploration
|
||||
- [ ] Publishing
|
||||
- [ ] Floodfill
|
||||
- [ ] LS2 and Encrypted Leasesets
|
||||
- Transports
|
||||
- [X] Transport manager
|
||||
- [ ] Transport manager
|
||||
- NTCP
|
||||
- [ ] Handshake
|
||||
- [ ] Session tracking
|
||||
- [ ] Automatic session creation
|
||||
- NTCP2
|
||||
- [ ] Handshake
|
||||
- [ ] Session tracking
|
||||
- [ ] Automatic session creation
|
||||
- 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
|
||||
- [ ] SSU
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
|
6
go.mod
6
go.mod
@ -3,9 +3,7 @@ module github.com/go-i2p/go-i2p
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/flynn/noise v1.1.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
|
||||
)
|
||||
|
63
go.sum
63
go.sum
@ -1,71 +1,24 @@
|
||||
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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
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=
|
||||
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/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
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=
|
||||
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
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=
|
||||
|
@ -1,6 +1,6 @@
|
||||
package bootstrap
|
||||
|
||||
import "github.com/go-i2p/go-i2p/lib/common/router_info"
|
||||
import "github.com/go-i2p/go-i2p/lib/common"
|
||||
|
||||
// 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 []router_info.RouterInfo, error)
|
||||
GetPeers(n int) (chan []common.RouterInfo, error)
|
||||
}
|
||||
|
@ -1,2 +1,4 @@
|
||||
//
|
||||
// provides generic interfaces for initial bootstrap into network and network reseeding
|
||||
//
|
||||
package bootstrap
|
||||
|
@ -1,23 +1,18 @@
|
||||
// Package base32 implmenets utilities for encoding and decoding text using I2P's alphabet
|
||||
//
|
||||
// base32 encoding using I2P's alphabet
|
||||
//
|
||||
package base32
|
||||
|
||||
import (
|
||||
b32 "encoding/base32"
|
||||
)
|
||||
|
||||
// I2PEncodeAlphabet is the base32 encoding used throughout I2P.
|
||||
// RFC 3548 using lowercase characters.
|
||||
const I2PEncodeAlphabet = "abcdefghijklmnopqrstuvwxyz234567"
|
||||
var I2PEncoding *b32.Encoding = b32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
|
||||
|
||||
// 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
|
||||
//
|
||||
// Return a go string of the I2P base32
|
||||
// encoding of the provided byte slice
|
||||
//
|
||||
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)
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,23 +1,30 @@
|
||||
// Package base64 implmenets utilities for encoding and decoding text using I2P's alphabet
|
||||
//
|
||||
// base64 encoding using I2P's alphabet
|
||||
//
|
||||
package base64
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
)
|
||||
|
||||
// I2PEncodeAlphabet is the base64 encoding used throughout I2P.
|
||||
// RFC 4648 with "/"" replaced with "~", and "+" replaced with "-".
|
||||
const I2PEncodeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"
|
||||
// i2p base64 alphabet
|
||||
const Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"
|
||||
|
||||
// I2PEncoding is the standard base64 encoding used through I2P.
|
||||
var I2PEncoding *b64.Encoding = b64.NewEncoding(I2PEncodeAlphabet)
|
||||
// i2p base64 encoding
|
||||
var I2PEncoding *b64.Encoding = b64.NewEncoding(Alphabet)
|
||||
|
||||
// I2PEncoding is the standard base64 encoding used through I2P.
|
||||
//
|
||||
// Return a go string of the I2P base64
|
||||
// encoding of the provided byte slice
|
||||
//
|
||||
func EncodeToString(data []byte) string {
|
||||
return I2PEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
// DecodeString decodes base64 string to []byte I2PEncoding
|
||||
func DecodeString(str string) ([]byte, error) {
|
||||
//
|
||||
// decode string using i2p base64 encoding
|
||||
// returns error if data is malfromed
|
||||
//
|
||||
func DecodeFromString(str string) (d []byte, err error) {
|
||||
return I2PEncoding.DecodeString(str)
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
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)
|
||||
}
|
220
lib/common/certificate.go
Normal file
220
lib/common/certificate.go
Normal file
@ -0,0 +1,220 @@
|
||||
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 CertificateInterface interface {
|
||||
Cert() []byte
|
||||
Length() (length int, err error)
|
||||
Data() (data []byte, err error)
|
||||
Type() (cert_type int, type_bytes []byte, err error)
|
||||
SignatureSize() (size int)
|
||||
}
|
||||
|
||||
type Certificate struct {
|
||||
CertType *Integer
|
||||
CertLen *Integer
|
||||
CertBytes []byte
|
||||
}
|
||||
|
||||
var ci CertificateInterface = &Certificate{}
|
||||
|
||||
func (certificate Certificate) SignatureSize() (size int) {
|
||||
return 40
|
||||
}
|
||||
|
||||
func (certificate Certificate) Cert() []byte {
|
||||
var ret []byte
|
||||
ret = append(ret, certificate.CertType.Bytes()...)
|
||||
l, _ := certificate.Length()
|
||||
//if err != nil && err.Error() != "certificate parsing warning: certificate data is shorter than specified by length" {
|
||||
//}
|
||||
data, _ := certificate.Data()
|
||||
if l != 0 && len(data) != 0 {
|
||||
ret = append(ret, certificate.CertLen.Bytes()...)
|
||||
ret = append(ret, data...)
|
||||
} else {
|
||||
ret = append(ret, certificate.CertLen.Bytes()...)
|
||||
}
|
||||
//log.Println("\n\n CERTIFICATE: ", ret, l+CERT_MIN_SIZE, err)
|
||||
return ret //[:l+CERT_MIN_SIZE]
|
||||
}
|
||||
|
||||
//
|
||||
// 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, type_bytes []byte, err error) {
|
||||
return certificate.CertType.Value(), certificate.CertType.Bytes(), nil
|
||||
}
|
||||
|
||||
//
|
||||
// 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) {
|
||||
if certificate.CertLen.Value() < 1 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Certificate) Length",
|
||||
"certificate_bytes_length": certificate.CertLen,
|
||||
"certificate_min_size": CERT_MIN_SIZE - 1,
|
||||
"reason": "certificate is too short",
|
||||
}).Warn("certificate format warning")
|
||||
err = errors.New("error parsing certificate length: certificate is too short")
|
||||
}
|
||||
if certificate.CertLen.Value() > len(certificate.CertBytes) {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Certificate) Length",
|
||||
"certificate_bytes_length": certificate.CertLen,
|
||||
"certificate_actual_length": len(certificate.CertBytes),
|
||||
"reason": "certificate data is shorter than specified by length",
|
||||
}).Warn("certificate format warning")
|
||||
err = errors.New("certificate parsing warning: certificate data is shorter than specified by length")
|
||||
length = certificate.CertLen.Value()
|
||||
}
|
||||
if certificate.CertLen.Value() < len(certificate.CertBytes) {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Certificate) Length",
|
||||
"certificate_bytes_length": certificate.CertLen,
|
||||
"certificate_actual_length": len(certificate.CertBytes),
|
||||
"reason": "certificate contains data beyond length",
|
||||
}).Warn("certificate format warning")
|
||||
err = errors.New("certificate parsing warning: certificate data is longer than specified by length")
|
||||
length = certificate.CertLen.Value()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
length = certificate.CertLen.Value()
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the Certificate data and any errors encountered parsing the Certificate.
|
||||
//
|
||||
func (certificate Certificate) Data() (data []byte, err error) {
|
||||
_, err = certificate.Length()
|
||||
data = certificate.CertBytes
|
||||
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.CertBytes
|
||||
return
|
||||
case "certificate parsing warning: certificate data is longer than specified by length":
|
||||
data = certificate.CertBytes[:certificate.CertLen.Value()]
|
||||
return
|
||||
}
|
||||
}
|
||||
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{}
|
||||
certificate.CertType, err = NewInteger(data[0:1])
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Certificate) ReadCertificate",
|
||||
"certificate": certificate,
|
||||
"data": data,
|
||||
"reason": "error parsing certificate type",
|
||||
"error": err,
|
||||
"error_reason": err.Error(),
|
||||
}).Warn("certificate format warning")
|
||||
}
|
||||
certificate.CertLen = &Integer{}
|
||||
cert_len := len(data)
|
||||
|
||||
if cert_len < CERT_MIN_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Certificate) ReadCertificate",
|
||||
"certificate_bytes_length": cert_len,
|
||||
"certificate_min_size": CERT_MIN_SIZE,
|
||||
"reason": "certificate is too short",
|
||||
}).Warn("certificate format warning")
|
||||
err = errors.New("error parsing certificate length: certificate is too short")
|
||||
return
|
||||
} else {
|
||||
certificate.CertLen, err = NewInteger(data[1:CERT_MIN_SIZE])
|
||||
// _, err = certificate.Type()
|
||||
//log.Println("Calculated len AT LEN", cert_len, "Stated len AT LEN", certificate.CertLen.Value())
|
||||
if err != nil {
|
||||
//return
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Certificate) ReadCertificate",
|
||||
"certificate_bytes_length": cert_len,
|
||||
"certificate_min_size": CERT_MIN_SIZE,
|
||||
"reason": "certificate size is invalid",
|
||||
}).Warn("certificate format warning")
|
||||
//err = errors.New("error parsing certificate type: certificate type is invalid")
|
||||
}
|
||||
certificate.CertBytes = data[CERT_MIN_SIZE:]
|
||||
_, err = certificate.Length()
|
||||
if err != nil {
|
||||
switch err.Error() {
|
||||
case "error parsing certificate length: certificate is too short":
|
||||
certificate.CertLen, err = NewInteger([]byte{00000000})
|
||||
return
|
||||
case "certificate parsing warning: certificate data is shorter than specified by length":
|
||||
return
|
||||
case "certificate parsing warning: certificate data is longer than specified by length":
|
||||
certificate.CertBytes = data[CERT_MIN_SIZE:]
|
||||
l, _ := certificate.Length()
|
||||
remainder = data[CERT_MIN_SIZE+l:]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package certificate
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -10,8 +10,11 @@ func TestCertificateTypeIsFirstByte(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x03, 0x00, 0x00}
|
||||
certificate, err := NewCertificate(bytes)
|
||||
cert_type := certificate.Type()
|
||||
certificate, _, err := ReadCertificate(bytes)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
cert_type, _, err := certificate.Type()
|
||||
|
||||
assert.Equal(cert_type, 3, "certificate.Type() should be the first bytes in a certificate")
|
||||
assert.Nil(err)
|
||||
@ -21,19 +24,25 @@ func TestCertificateLengthCorrect(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x03, 0x00, 0x02, 0xff, 0xff}
|
||||
certificate, err := NewCertificate(bytes)
|
||||
cert_len := certificate.Length()
|
||||
certificate, _, err := ReadCertificate(bytes)
|
||||
assert.Nil(err, "ReadCertificate() should not return an error with valid data")
|
||||
|
||||
cert_len, err := certificate.Length()
|
||||
assert.Nil(err, "ReadCertificate() should not return an error with valid data")
|
||||
|
||||
assert.Equal(cert_len, 2, "certificate.Length() should return integer from second two bytes")
|
||||
assert.Nil(err)
|
||||
assert.Nil(err, "ReadCertificate() should not return an error with valid data")
|
||||
}
|
||||
|
||||
func TestCertificateLengthErrWhenTooShort(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x03, 0x01}
|
||||
certificate, err := NewCertificate(bytes)
|
||||
cert_len := certificate.Length()
|
||||
certificate, _, err := ReadCertificate(bytes)
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing certificate length: certificate is too short", err.Error(), "correct error message should be returned")
|
||||
}
|
||||
cert_len, err := certificate.Length()
|
||||
|
||||
assert.Equal(cert_len, 0, "certificate.Length() did not return zero length for missing length data")
|
||||
if assert.NotNil(err) {
|
||||
@ -45,8 +54,15 @@ func TestCertificateLengthErrWhenDataTooShort(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x03, 0x00, 0x02, 0xff}
|
||||
certificate, err := NewCertificate(bytes)
|
||||
cert_len := certificate.Length()
|
||||
certificate, _, err := ReadCertificate(bytes)
|
||||
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")
|
||||
}
|
||||
|
||||
cert_len, err := certificate.Length()
|
||||
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")
|
||||
}
|
||||
|
||||
assert.Equal(cert_len, 2, "certificate.Length() did not return indicated length when data was actually missing")
|
||||
if assert.NotNil(err) {
|
||||
@ -58,28 +74,34 @@ func TestCertificateDataWhenCorrectSize(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x03, 0x00, 0x01, 0xaa}
|
||||
certificate, err := NewCertificate(bytes)
|
||||
cert_data := certificate.Data()
|
||||
certificate, _, err := ReadCertificate(bytes)
|
||||
assert.Nil(err, "certificate.Data() returned error with valid data")
|
||||
cert_len, err := certificate.Length()
|
||||
|
||||
assert.Nil(err, "certificate.Data() returned error with valid data")
|
||||
cert_len := len(cert_data)
|
||||
|
||||
assert.Equal(cert_len, 1, "certificate.Length() did not return indicated length when data was valid")
|
||||
assert.Equal(170, int(cert_data[0]), "certificate.Data() returned incorrect data")
|
||||
data, _ := NewInteger(certificate.CertBytes)
|
||||
assert.Equal(170, data.Value(), "certificate.Data() returned incorrect data")
|
||||
}
|
||||
|
||||
func TestCertificateDataWhenTooLong(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x03, 0x00, 0x02, 0xff, 0xff, 0xaa, 0xaa}
|
||||
certificate, err := NewCertificate(bytes)
|
||||
cert_data := certificate.Data()
|
||||
certificate, _, err := ReadCertificate(bytes)
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is longer than specified by length", err.Error(), "correct error message should be returned")
|
||||
}
|
||||
|
||||
cert_len, err := certificate.Length()
|
||||
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is longer than specified by length", err.Error(), "correct error message should be returned")
|
||||
}
|
||||
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 {
|
||||
if certificate.CertBytes[0] != 0xff || certificate.CertBytes[1] != 0xff {
|
||||
t.Fatal("certificate.Data() returned incorrect data when data was too long")
|
||||
}
|
||||
}
|
||||
@ -88,8 +110,11 @@ func TestCertificateDataWhenTooShort(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x03, 0x00, 0x02, 0xff}
|
||||
certificate, err := NewCertificate(bytes)
|
||||
cert_data := certificate.Data()
|
||||
certificate, _, err := ReadCertificate(bytes)
|
||||
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")
|
||||
}
|
||||
cert_data, err := 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")
|
||||
@ -105,7 +130,8 @@ func TestReadCertificateWithCorrectData(t *testing.T) {
|
||||
bytes := []byte{0x00, 0x00, 0x02, 0xff, 0xff}
|
||||
cert, remainder, err := ReadCertificate(bytes)
|
||||
|
||||
assert.Equal(cert.length(), 5, "ReadCertificate() did not return correct amount of data for valid certificate")
|
||||
t.Log("CERT IS:", cert.Cert())
|
||||
assert.Equal(len(cert.Cert()), 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")
|
||||
}
|
||||
@ -116,7 +142,7 @@ func TestReadCertificateWithDataTooShort(t *testing.T) {
|
||||
bytes := []byte{0x00, 0x00, 0x02, 0xff}
|
||||
cert, remainder, err := ReadCertificate(bytes)
|
||||
|
||||
assert.Equal(cert.length(), 4, "ReadCertificate() did not return correct amount of data for certificate with missing data")
|
||||
assert.Equal(len(cert.Cert()), 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")
|
||||
@ -129,10 +155,10 @@ func TestReadCertificateWithRemainder(t *testing.T) {
|
||||
bytes := []byte{0x00, 0x00, 0x02, 0xff, 0xff, 0x01}
|
||||
cert, remainder, err := ReadCertificate(bytes)
|
||||
|
||||
assert.Equal(cert.length(), 5, "ReadCertificate() did not return correct amount of data for certificate with extra data")
|
||||
assert.Equal(len(cert.Cert()), 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.Nil(err)
|
||||
assert.Equal(1, int(remainder[0]), "ReadCertificate() did not return correct remainder value")
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestReadCertificateWithInvalidLength(t *testing.T) {
|
||||
@ -141,7 +167,7 @@ func TestReadCertificateWithInvalidLength(t *testing.T) {
|
||||
bytes := []byte{0x00, 0x00}
|
||||
cert, remainder, err := ReadCertificate(bytes)
|
||||
|
||||
assert.Equal(cert.length(), 2, "ReadCertificate() should populate the certificate with the provided data even when invalid")
|
||||
assert.Equal(len(cert.Cert()), 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")
|
@ -1,71 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIntegerBigEndian(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
|
||||
integer := Integer(bytes)
|
||||
|
||||
assert.Equal(integer.Int(), 1, "Integer() did not parse bytes big endian")
|
||||
}
|
||||
|
||||
func TestWorksWithOneByte(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
integer := Integer([]byte{0x01})
|
||||
|
||||
assert.Equal(integer.Int(), 1, "Integer() did not correctly parse single byte slice")
|
||||
}
|
||||
|
||||
func TestIsZeroWithNoData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
integer := Integer([]byte{})
|
||||
|
||||
assert.Equal(integer.Int(), 0, "Integer() did not correctly parse zero length byte slice")
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
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")
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
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
|
||||
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
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
|
||||
}
|
45
lib/common/date.go
Normal file
45
lib/common/date.go
Normal file
@ -0,0 +1,45 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
I2P Date
|
||||
https://geti2p.net/spec/common-structures#date
|
||||
Accurate for version 0.9.24
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Date [8]byte
|
||||
|
||||
const DATE_SIZE = 8
|
||||
|
||||
//
|
||||
// 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, _ := NewInteger(date[:])
|
||||
date_time = time.Unix(0, int64(seconds.Value()*1000000))
|
||||
return
|
||||
}
|
||||
|
||||
func ReadDate(data []byte) (h Date, remainder []byte, err error) {
|
||||
if len(data) < DATE_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Date) ReadDate",
|
||||
"data_len": len(data),
|
||||
"required_len": "8",
|
||||
"reason": "date missing data",
|
||||
}).Error("date error")
|
||||
err = errors.New("error reading date, insufficient length")
|
||||
copy(h[:], data[0:len(data)-1])
|
||||
} else {
|
||||
copy(h[:], data[0:DATE_SIZE-1])
|
||||
copy(remainder, data[DATE_SIZE-1:])
|
||||
}
|
||||
return
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package data
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTimeFromMiliseconds(t *testing.T) {
|
62
lib/common/destination.go
Normal file
62
lib/common/destination.go
Normal file
@ -0,0 +1,62 @@
|
||||
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 struct {
|
||||
KeysAndCert
|
||||
}
|
||||
|
||||
func (destination Destination) PublicKey() (crypto.PublicKey, error) {
|
||||
return destination.KeysAndCert.GetPublicKey()
|
||||
}
|
||||
|
||||
func (destination Destination) SigningPublicKey() (crypto.SigningPublicKey, error) {
|
||||
return destination.KeysAndCert.GetSigningPublicKey()
|
||||
}
|
||||
|
||||
func (destination Destination) Certificate() (CertificateInterface, error) {
|
||||
return destination.KeysAndCert.GetCertificate()
|
||||
}
|
||||
|
||||
//
|
||||
// Generate the I2P base32 address for this Destination.
|
||||
//
|
||||
func (destination Destination) Base32Address() (str string) {
|
||||
hash := crypto.SHA256(destination.Cert())
|
||||
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.Cert())
|
||||
}
|
||||
|
||||
func ReadDestination(data []byte) (destination Destination, remainder []byte, err error) {
|
||||
keys_and_cert, remainder, err := ReadKeysAndCert(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
destination.KeysAndCert = keys_and_cert
|
||||
return
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
FROM golang
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y
|
||||
apt-get upgrade -y && \
|
||||
apt-get install libsodium-dev -y
|
||||
|
||||
RUN go get github.com/dvyukov/go-fuzz/go-fuzz
|
||||
RUN go get github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
|
@ -1,9 +1,9 @@
|
||||
package exportable
|
||||
|
||||
import common "github.com/go-i2p/go-i2p/lib/common/certificate"
|
||||
import "github.com/go-i2p/go-i2p/lib/common"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
cert, _, _ := common.ReadCertificate(data)
|
||||
cert := common.Certificate(data)
|
||||
cert.Data()
|
||||
cert.Length()
|
||||
cert.Type()
|
||||
|
@ -1,9 +1,9 @@
|
||||
package exportable
|
||||
|
||||
import common "github.com/go-i2p/go-i2p/lib/common/destination"
|
||||
import "github.com/go-i2p/go-i2p/lib/common"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
destination, _, _ := common.ReadDestination(data)
|
||||
destination := common.Destination(data)
|
||||
destination.Base32Address()
|
||||
destination.Base64()
|
||||
return 0
|
||||
|
@ -1,9 +1,9 @@
|
||||
package exportable
|
||||
|
||||
import common "github.com/go-i2p/go-i2p/lib/common/keys_and_cert"
|
||||
import "github.com/go-i2p/go-i2p/lib/common"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
keys_and_cert, _, _ := common.NewKeysAndCert(data)
|
||||
keys_and_cert, _, _ := common.ReadKeysAndCert(data)
|
||||
keys_and_cert.Certificate()
|
||||
keys_and_cert.PublicKey()
|
||||
keys_and_cert.SigningPublicKey()
|
||||
|
@ -1,6 +1,6 @@
|
||||
package exportable
|
||||
|
||||
import common "github.com/go-i2p/go-i2p/lib/common/router_address"
|
||||
import "github.com/go-i2p/go-i2p/lib/common"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
router_address, _, _ := common.ReadRouterAddress(data)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package exportable
|
||||
|
||||
import common "github.com/go-i2p/go-i2p/lib/common/router_identity"
|
||||
import "github.com/go-i2p/go-i2p/lib/common"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
router_identity, _, _ := common.ReadRouterIdentity(data)
|
||||
|
@ -1,9 +1,9 @@
|
||||
package exportable
|
||||
|
||||
import common "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
import "github.com/go-i2p/go-i2p/lib/common"
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
str := common.I2PString(data)
|
||||
str, _, _ := common.ReadString(data)
|
||||
str.Data()
|
||||
str.Length()
|
||||
str, _ = common.ToI2PString(string(data))
|
||||
|
48
lib/common/hash.go
Normal file
48
lib/common/hash.go
Normal file
@ -0,0 +1,48 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
)
|
||||
|
||||
const HASH_SIZE = 32
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func ReadHash(data []byte) (h Hash, remainder []byte, err error) {
|
||||
if len(data) < HASH_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Hash) ReadHash",
|
||||
"data_len": len(data),
|
||||
"required_len": "32",
|
||||
"reason": "hash missing data",
|
||||
}).Error("hash error")
|
||||
err = errors.New("error reading hash, insufficient length")
|
||||
copy(h[:], data[0:len(data)-1])
|
||||
} else {
|
||||
copy(h[:], data[0:HASH_SIZE-1])
|
||||
copy(remainder, data[HASH_SIZE-1:])
|
||||
}
|
||||
return
|
||||
}
|
33
lib/common/ident.go
Normal file
33
lib/common/ident.go
Normal file
@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
I2P Tunnel Identity Helpers
|
||||
https://geti2p.net/spec/common-structures#ident
|
||||
Accurate for version 0.9.24
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Ident [4]byte
|
||||
|
||||
const IDENT_SIZE = 4
|
||||
|
||||
func ReadIdent(data []byte) (h Ident, remainder []byte, err error) {
|
||||
if len(data) < IDENT_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Ident) ReadIdent",
|
||||
"data_len": len(data),
|
||||
"required_len": "8",
|
||||
"reason": "ident missing data",
|
||||
}).Error("ident error")
|
||||
err = errors.New("error reading ident, insufficient length")
|
||||
copy(h[:], data[0:len(data)-1])
|
||||
} else {
|
||||
copy(h[:], data[0:IDENT_SIZE-1])
|
||||
copy(remainder, data[IDENT_SIZE-1:])
|
||||
}
|
||||
return
|
||||
}
|
83
lib/common/integer.go
Normal file
83
lib/common/integer.go
Normal file
@ -0,0 +1,83 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
I2P Integer
|
||||
https://geti2p.net/spec/common-structures#integer
|
||||
Accurate for version 0.9.24
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
// log "github.com/sirupsen/logrus"
|
||||
// "errors"
|
||||
)
|
||||
|
||||
// Total byte length of an I2P integer
|
||||
const (
|
||||
INTEGER_SIZE = 8
|
||||
)
|
||||
|
||||
type Integer []byte
|
||||
|
||||
func (i *Integer) longBytes() (value [INTEGER_SIZE]byte) {
|
||||
value = [INTEGER_SIZE]byte{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
pad := INTEGER_SIZE - len([]byte(*i))
|
||||
for index, element := range []byte(*i) {
|
||||
value[pad+index] = element
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (i *Integer) Value() int {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
r := i.longBytes()
|
||||
// log.Println("LONG BYTES", r)
|
||||
return int(binary.BigEndian.Uint64(r[:]))
|
||||
// return int(binary.BigEndian.Int64(r[:]))
|
||||
}
|
||||
|
||||
func (i *Integer) Bytes() []byte {
|
||||
if i == nil {
|
||||
return []byte{}
|
||||
}
|
||||
if len([]byte(*i)) == 0 {
|
||||
return []byte{0}
|
||||
}
|
||||
r := []byte(*i)
|
||||
return r
|
||||
}
|
||||
|
||||
//
|
||||
// Interpret a slice of bytes from length 0 to length 8 as a big-endian
|
||||
// integer and return an int representation.
|
||||
//
|
||||
func NewInteger(number []byte) (value *Integer, err error) {
|
||||
var integer Integer = number
|
||||
value = &integer //[INTEGER_SIZE]byte(number)
|
||||
// for index, element := range number {
|
||||
// value[INTEGER_SIZE-1-index] = element
|
||||
// }
|
||||
/*length := len(number)
|
||||
if length < INTEGER_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Integer) NewInteger",
|
||||
"length": length,
|
||||
"required_len": INTEGER_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing Integer")
|
||||
err = errors.New("error parsing Integer, not enough data")
|
||||
}else if length > INTEGER_SIZE{
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Integer) NewInteger",
|
||||
"length": length,
|
||||
"required_len": INTEGER_SIZE,
|
||||
"reason": "too much data",
|
||||
}).Error("error parsing Integer")
|
||||
err = errors.New("error parsing Integer, too much data")
|
||||
}else{
|
||||
err = nil
|
||||
}*/
|
||||
return
|
||||
}
|
34
lib/common/integer_test.go
Normal file
34
lib/common/integer_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerBigEndian(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
|
||||
integer, err := NewInteger(bytes)
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Equal(integer.Value(), 1, "Integer() did not parse bytes big endian")
|
||||
|
||||
checkbytes := integer.Bytes()
|
||||
|
||||
assert.Equal(bytes, checkbytes, "IntegerBytes() did not match original bytes")
|
||||
}
|
||||
|
||||
func TestWorksWithOneByte(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x00}
|
||||
integer, err := NewInteger(bytes)
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Equal(integer.Value(), 0, "Integer() did not correctly parse single byte slice")
|
||||
|
||||
checkbytes := integer.Bytes()
|
||||
|
||||
assert.Equal(bytes, checkbytes, "IntegerBytes() did not match original bytes")
|
||||
}
|
303
lib/common/key_certificate.go
Normal file
303
lib/common/key_certificate.go
Normal file
@ -0,0 +1,303 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
I2P Key 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"
|
||||
|
||||
"github.com/go-i2p/go-i2p/lib/crypto"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Key Certificate Signing Key Types
|
||||
const (
|
||||
KEYCERT_SIGN_DSA_SHA1 = iota
|
||||
KEYCERT_SIGN_P256
|
||||
KEYCERT_SIGN_P384
|
||||
KEYCERT_SIGN_P521
|
||||
KEYCERT_SIGN_RSA2048
|
||||
KEYCERT_SIGN_RSA3072
|
||||
KEYCERT_SIGN_RSA4096
|
||||
KEYCERT_SIGN_ED25519
|
||||
KEYCERT_SIGN_ED25519PH
|
||||
)
|
||||
|
||||
// Key Certificate Public Key Types
|
||||
const (
|
||||
KEYCERT_CRYPTO_ELG = iota
|
||||
KEYCERT_CRYPTO_P256
|
||||
KEYCERT_CRYPTO_P384
|
||||
KEYCERT_CRYPTO_P521
|
||||
KEYCERT_CRYPTO_X25519
|
||||
)
|
||||
|
||||
// SigningPublicKey sizes for Signing Key Types
|
||||
const (
|
||||
KEYCERT_SIGN_DSA_SHA1_SIZE = 128
|
||||
KEYCERT_SIGN_P256_SIZE = 64
|
||||
KEYCERT_SIGN_P384_SIZE = 96
|
||||
KEYCERT_SIGN_P521_SIZE = 132
|
||||
KEYCERT_SIGN_RSA2048_SIZE = 256
|
||||
KEYCERT_SIGN_RSA3072_SIZE = 384
|
||||
KEYCERT_SIGN_RSA4096_SIZE = 512
|
||||
KEYCERT_SIGN_ED25519_SIZE = 32
|
||||
KEYCERT_SIGN_ED25519PH_SIZE = 32
|
||||
)
|
||||
|
||||
// PublicKey sizes for Public Key Types
|
||||
const (
|
||||
KEYCERT_CRYPTO_ELG_SIZE = 256
|
||||
)
|
||||
|
||||
// Sizes of structures in KeyCertificates
|
||||
const (
|
||||
KEYCERT_PUBKEY_SIZE = 256
|
||||
KEYCERT_SPK_SIZE = 128
|
||||
)
|
||||
|
||||
const (
|
||||
KEYCERT_MIN_SIZE = 7
|
||||
)
|
||||
|
||||
type KeyCertificate struct {
|
||||
CertificateInterface
|
||||
PKType *Integer
|
||||
PKExtra []byte
|
||||
SPKType *Integer
|
||||
SPKExtra []byte
|
||||
} //[]byte
|
||||
|
||||
//
|
||||
// The data contained in the Key Certificate.
|
||||
//
|
||||
func (key_certificate KeyCertificate) Data() ([]byte, error) {
|
||||
var r []byte
|
||||
r = append(r, key_certificate.CertificateInterface.Cert()...)
|
||||
r = append(r, key_certificate.PKType.Bytes()...)
|
||||
r = append(r, key_certificate.SPKType.Bytes()...)
|
||||
return r, 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) {
|
||||
// signing_key_type := key_certificate.SPKType
|
||||
// data_len := len(key_certificate.CertificateInterface.CertBytes)
|
||||
if len(key_certificate.SPKType.Bytes()) < 2 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) SigningPublicKeyType",
|
||||
"data_len": len(key_certificate.SPKType.Bytes()),
|
||||
"required_len": 2,
|
||||
"reason": "not enough data",
|
||||
}).Error("error retrieving Signing Public Key type")
|
||||
err = errors.New("error retrieving signing public key type: not enough data")
|
||||
return
|
||||
}
|
||||
log.Println("Signing Public Key Type", key_certificate.SPKType) //.Value())
|
||||
return key_certificate.SPKType.Value(), nil
|
||||
}
|
||||
|
||||
//
|
||||
// The PublicKey type this Key Certificate describes and any errors encountered parsing
|
||||
// this KeyCertificate.
|
||||
//
|
||||
func (key_certificate KeyCertificate) PublicKeyType() (pubkey_type int, err error) {
|
||||
if len(key_certificate.PKType.Bytes()) < 2 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) SingingPublicKeyType",
|
||||
"data_len": len(key_certificate.PKType.Bytes()),
|
||||
"required_len": 2,
|
||||
"reason": "not enough data",
|
||||
}).Error("error retrieving Singning Public Key type")
|
||||
err = errors.New("error retrieving signing public key type: not enough data")
|
||||
return
|
||||
}
|
||||
log.Println("Public Key Type", key_certificate.PKType)
|
||||
return key_certificate.PKType.Value(), nil
|
||||
}
|
||||
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
func (key_certificate KeyCertificate) ConstructPublicKey(data []byte) (public_key crypto.PublicKey, err error) {
|
||||
key_type, err := key_certificate.SigningPublicKeyType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data_len := len(data)
|
||||
if data_len < KEYCERT_PUBKEY_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) ConstructPublicKey",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYCERT_PUBKEY_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error constructing public key")
|
||||
err = errors.New("error constructing public key: not enough data")
|
||||
return
|
||||
}
|
||||
switch key_type {
|
||||
case KEYCERT_CRYPTO_ELG:
|
||||
var elg_key crypto.ElgPublicKey
|
||||
copy(elg_key[:], data[KEYCERT_PUBKEY_SIZE-KEYCERT_CRYPTO_ELG_SIZE:KEYCERT_PUBKEY_SIZE])
|
||||
public_key = elg_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.
|
||||
//
|
||||
func (key_certificate KeyCertificate) ConstructSigningPublicKey(data []byte) (signing_public_key crypto.SigningPublicKey, err error) {
|
||||
signing_key_type, err := key_certificate.PublicKeyType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data_len := len(data)
|
||||
if data_len < KEYCERT_SPK_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) ConstructSigningPublicKey",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYCERT_SPK_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error constructing signing public key")
|
||||
err = errors.New("error constructing signing public key: not enough data")
|
||||
return
|
||||
}
|
||||
switch signing_key_type {
|
||||
case KEYCERT_SIGN_DSA_SHA1:
|
||||
var dsa_key crypto.DSAPublicKey
|
||||
copy(dsa_key[:], data[KEYCERT_SPK_SIZE-KEYCERT_SIGN_DSA_SHA1_SIZE:KEYCERT_SPK_SIZE])
|
||||
signing_public_key = dsa_key
|
||||
case KEYCERT_SIGN_P256:
|
||||
var ec_key crypto.ECP256PublicKey
|
||||
copy(ec_key[:], data[KEYCERT_SPK_SIZE-KEYCERT_SIGN_P256_SIZE:KEYCERT_SPK_SIZE])
|
||||
signing_public_key = ec_key
|
||||
case KEYCERT_SIGN_P384:
|
||||
var ec_key crypto.ECP384PublicKey
|
||||
copy(ec_key[:], data[KEYCERT_SPK_SIZE-KEYCERT_SIGN_P384_SIZE:KEYCERT_SPK_SIZE])
|
||||
signing_public_key = ec_key
|
||||
case KEYCERT_SIGN_P521:
|
||||
var ec_key crypto.ECP521PublicKey
|
||||
extra := KEYCERT_SIGN_P521_SIZE - KEYCERT_SPK_SIZE
|
||||
copy(ec_key[:], data)
|
||||
d, _ := key_certificate.Data()
|
||||
copy(ec_key[KEYCERT_SPK_SIZE:], d[4:4+extra])
|
||||
signing_public_key = ec_key
|
||||
case KEYCERT_SIGN_RSA2048:
|
||||
//var rsa_key crypto.RSA2048PublicKey
|
||||
//extra := KEYCERT_SIGN_RSA2048_SIZE - 128
|
||||
//copy(rsa_key[:], data)
|
||||
//copy(rsa_key[128:], key_certificate[4:4+extra])
|
||||
//signing_public_key = rsa_key
|
||||
case KEYCERT_SIGN_RSA3072:
|
||||
case KEYCERT_SIGN_RSA4096:
|
||||
case KEYCERT_SIGN_ED25519:
|
||||
case KEYCERT_SIGN_ED25519PH:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// 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
|
||||
}
|
||||
return sizes[int(key_type)]
|
||||
}
|
||||
|
||||
//
|
||||
// Read a KeyCertificate from a slice of bytes
|
||||
//
|
||||
func ReadKeyCertificate(data []byte) (key_certificate KeyCertificate, err error) {
|
||||
key_certificate.SPKType = &Integer{}
|
||||
key_certificate.PKType = &Integer{}
|
||||
cert, remainder, err := ReadCertificate(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cert_type, _, err := cert.Type()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Println("KEYSANDCERT CERT TYPE=", cert_type, cert.CertBytes, remainder)
|
||||
key_certificate.CertificateInterface = cert
|
||||
data = cert.Cert()
|
||||
data_len := len(data)
|
||||
if data_len < KEYCERT_MIN_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) PublicKeyType",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYCERT_MIN_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing key certificate public key")
|
||||
err = errors.New("error parsing key certificate public key: not enough data")
|
||||
return
|
||||
}
|
||||
log.Println("KEYSANDCERT=", data, "| len=", data_len, "| 0=", data[0], "| 1=", data[1])
|
||||
key_certificate.SPKType, err = NewInteger(data[len(data)-2 : len(data)])
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) SigningPublicKeyType",
|
||||
"key_type": key_certificate.PKType,
|
||||
"reason": "failed to read signing public key type",
|
||||
}).Error("error parsing key certificate signing public key")
|
||||
}
|
||||
key_certificate.PKType, err = NewInteger(data[len(data)-4 : len(data)-2])
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) PublicKeyType",
|
||||
"key_type": key_certificate.PKType,
|
||||
"reason": "failed to read public key type",
|
||||
}).Error("error parsing key certificate public key")
|
||||
err = errors.New("error parsing key certificate public key: not enough data")
|
||||
}
|
||||
return
|
||||
}
|
@ -1,252 +0,0 @@
|
||||
// Package key_certificate implements the I2P Destination common data structure
|
||||
package key_certificate
|
||||
|
||||
/*
|
||||
I2P Key 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"
|
||||
|
||||
. "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"
|
||||
)
|
||||
|
||||
// Key Certificate Signing Key Types
|
||||
const (
|
||||
KEYCERT_SIGN_DSA_SHA1 = iota
|
||||
KEYCERT_SIGN_P256
|
||||
KEYCERT_SIGN_P384
|
||||
KEYCERT_SIGN_P521
|
||||
KEYCERT_SIGN_RSA2048
|
||||
KEYCERT_SIGN_RSA3072
|
||||
KEYCERT_SIGN_RSA4096
|
||||
KEYCERT_SIGN_ED25519
|
||||
KEYCERT_SIGN_ED25519PH
|
||||
)
|
||||
|
||||
// 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
|
||||
const (
|
||||
KEYCERT_SIGN_DSA_SHA1_SIZE = 128
|
||||
KEYCERT_SIGN_P256_SIZE = 64
|
||||
KEYCERT_SIGN_P384_SIZE = 96
|
||||
KEYCERT_SIGN_P521_SIZE = 132
|
||||
KEYCERT_SIGN_RSA2048_SIZE = 256
|
||||
KEYCERT_SIGN_RSA3072_SIZE = 384
|
||||
KEYCERT_SIGN_RSA4096_SIZE = 512
|
||||
KEYCERT_SIGN_ED25519_SIZE = 32
|
||||
KEYCERT_SIGN_ED25519PH_SIZE = 32
|
||||
)
|
||||
|
||||
// PublicKey sizes for Public Key Types
|
||||
const (
|
||||
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
|
||||
const (
|
||||
KEYCERT_PUBKEY_SIZE = 256
|
||||
KEYCERT_SPK_SIZE = 128
|
||||
)
|
||||
|
||||
// type KeyCertificate []byte
|
||||
type KeyCertificate struct {
|
||||
*Certificate
|
||||
spkType Integer
|
||||
cpkType Integer
|
||||
}
|
||||
|
||||
// Data returns the raw []byte contained in the Certificate.
|
||||
func (key_certificate KeyCertificate) Data() ([]byte, error) {
|
||||
return key_certificate.Certificate.RawBytes(), nil
|
||||
}
|
||||
|
||||
// SigningPublicKeyType returns the SigningPublicKey type as a Go integer.
|
||||
func (key_certificate KeyCertificate) SigningPublicKeyType() (signing_pubkey_type int) {
|
||||
return key_certificate.spkType.Int()
|
||||
}
|
||||
|
||||
// PublicKeyType returns the PublicKey type as a Go integer.
|
||||
func (key_certificate KeyCertificate) PublicKeyType() (pubkey_type int) {
|
||||
return key_certificate.cpkType.Int()
|
||||
}
|
||||
|
||||
// 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 := key_certificate.PublicKeyType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data_len := len(data)
|
||||
if data_len < key_certificate.CryptoSize() {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) ConstructPublicKey",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYCERT_PUBKEY_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error constructing public key")
|
||||
err = errors.New("error constructing public key: not enough data")
|
||||
return
|
||||
}
|
||||
switch key_type {
|
||||
case KEYCERT_CRYPTO_ELG:
|
||||
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
|
||||
}
|
||||
|
||||
// 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 := key_certificate.PublicKeyType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data_len := len(data)
|
||||
if data_len < key_certificate.SignatureSize() {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeyCertificate) ConstructSigningPublicKey",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYCERT_SPK_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error constructing signing public key")
|
||||
err = errors.New("error constructing signing public key: not enough data")
|
||||
return
|
||||
}
|
||||
switch signing_key_type {
|
||||
case KEYCERT_SIGN_DSA_SHA1:
|
||||
var dsa_key crypto.DSAPublicKey
|
||||
copy(dsa_key[:], data[KEYCERT_SPK_SIZE-KEYCERT_SIGN_DSA_SHA1_SIZE:KEYCERT_SPK_SIZE])
|
||||
signing_public_key = dsa_key
|
||||
case KEYCERT_SIGN_P256:
|
||||
var ec_key crypto.ECP256PublicKey
|
||||
copy(ec_key[:], data[KEYCERT_SPK_SIZE-KEYCERT_SIGN_P256_SIZE:KEYCERT_SPK_SIZE])
|
||||
signing_public_key = ec_key
|
||||
case KEYCERT_SIGN_P384:
|
||||
var ec_key crypto.ECP384PublicKey
|
||||
copy(ec_key[:], data[KEYCERT_SPK_SIZE-KEYCERT_SIGN_P384_SIZE:KEYCERT_SPK_SIZE])
|
||||
signing_public_key = ec_key
|
||||
case KEYCERT_SIGN_P521:
|
||||
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.Certificate.RawBytes()[4:4+extra])
|
||||
signing_public_key = ec_key
|
||||
case KEYCERT_SIGN_RSA2048:
|
||||
//var rsa_key crypto.RSA2048PublicKey
|
||||
//extra := KEYCERT_SIGN_RSA2048_SIZE - 128
|
||||
//copy(rsa_key[:], data)
|
||||
//copy(rsa_key[128:], key_certificate[4:4+extra])
|
||||
//signing_public_key = rsa_key
|
||||
case KEYCERT_SIGN_RSA3072:
|
||||
case KEYCERT_SIGN_RSA4096:
|
||||
case KEYCERT_SIGN_ED25519:
|
||||
case KEYCERT_SIGN_ED25519PH:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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: 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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package key_certificate
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -9,52 +9,60 @@ import (
|
||||
func TestSingingPublicKeyTypeReturnsCorrectInteger(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00})
|
||||
pk_type := key_cert.SigningPublicKeyType()
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00})
|
||||
assert.Nil(err, "ReadKeyCertificate() returned error with valid data")
|
||||
spk_type, err := 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")
|
||||
assert.Equal(spk_type, KEYCERT_SIGN_DSA_SHA1, "SigningPublicKeyType() did not return correct type")
|
||||
}
|
||||
|
||||
func TestSingingPublicKeyTypeReportsWhenDataTooSmall(t *testing.T) {
|
||||
func TestPublicKeyTypeReportsWhenDataTooSmall(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
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")
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x01, 0x00})
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing key certificate public key: not enough data", err.Error(), "correct error message should be returned")
|
||||
}
|
||||
// assert.NotNil(err, "ReadKeyCertificate() returned error with valid data")
|
||||
_, err = 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("error retrieving signing public key type: not enough data", err.Error(), "correct error message should be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyTypeReturnsCorrectInteger(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03})
|
||||
pk_type := key_cert.PublicKeyType()
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00})
|
||||
assert.Nil(err, "ReadKeyCertificate() returned error with valid data")
|
||||
pk_type, err := 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")
|
||||
assert.Nil(err, "PublicKeyType() returned error with valid data")
|
||||
assert.Equal(pk_type, KEYCERT_CRYPTO_P521, "PublicKeyType() did not return correct type")
|
||||
}
|
||||
|
||||
func TestPublicKeyTypeReportsWhenDataTooSmall(t *testing.T) {
|
||||
func TestSigningPublicKeyTypeReportsWhenDataTooSmall(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x02, 0x00, 0x00})
|
||||
pk_type := key_cert.PublicKeyType()
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x02, 0x00, 0x00})
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing key certificate public key: not enough data", err.Error(), "correct error message should be returned")
|
||||
}
|
||||
_, err = key_cert.SigningPublicKeyType()
|
||||
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing key certificate: not enough data", err.Error(), "correct error message should be returned")
|
||||
assert.Equal("error retrieving signing public key type: 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, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
assert.Nil(err, "ReadKeyCertificate() returned error with valid data")
|
||||
data := make([]byte, 255)
|
||||
_, err = key_cert.ConstructPublicKey(data)
|
||||
|
||||
@ -62,22 +70,25 @@ func TestConstructPublicKeyReportsWhenDataTooSmall(t *testing.T) {
|
||||
assert.Equal("error constructing public key: not enough data", err.Error(), "correct error message should be returned")
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
/*
|
||||
func TestConstructPublicKeyReturnsCorrectDataWithElg(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
assert.Nil(err, "ReadKeyCertificate() returned error with valid data")
|
||||
data := make([]byte, 256)
|
||||
pk, err := key_cert.ConstructPublicKey(data)
|
||||
|
||||
assert.Nil(err, "ConstructPublicKey() returned error with valid data")
|
||||
assert.Equal(pk.Len(), 256, "ConstructPublicKey() did not return public key with correct length")
|
||||
}
|
||||
|
||||
*/
|
||||
/*
|
||||
func TestConstructSigningPublicKeyReportsWhenDataTooSmall(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
data := make([]byte, 127)
|
||||
_, err = key_cert.ConstructSigningPublicKey(data)
|
||||
|
||||
@ -89,7 +100,7 @@ func TestConstructSigningPublicKeyReportsWhenDataTooSmall(t *testing.T) {
|
||||
func TestConstructSigningPublicKeyWithDSASHA1(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00})
|
||||
data := make([]byte, 128)
|
||||
spk, err := key_cert.ConstructSigningPublicKey(data)
|
||||
|
||||
@ -100,7 +111,7 @@ func TestConstructSigningPublicKeyWithDSASHA1(t *testing.T) {
|
||||
func TestConstructSigningPublicKeyWithP256(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01})
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01})
|
||||
data := make([]byte, 128)
|
||||
spk, err := key_cert.ConstructSigningPublicKey(data)
|
||||
|
||||
@ -111,7 +122,7 @@ func TestConstructSigningPublicKeyWithP256(t *testing.T) {
|
||||
func TestConstructSigningPublicKeyWithP384(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02})
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02})
|
||||
data := make([]byte, 128)
|
||||
spk, err := key_cert.ConstructSigningPublicKey(data)
|
||||
|
||||
@ -122,10 +133,11 @@ func TestConstructSigningPublicKeyWithP384(t *testing.T) {
|
||||
func TestConstructSigningPublicKeyWithP521(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00})
|
||||
key_cert, err := ReadKeyCertificate([]byte{0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00})
|
||||
data := make([]byte, 128)
|
||||
spk, err := key_cert.ConstructSigningPublicKey(data)
|
||||
|
||||
assert.Nil(err, "ConstructSigningPublicKey() with P521 returned err on valid data")
|
||||
assert.Equal(spk.Len(), KEYCERT_SIGN_P521_SIZE, "ConstructSigningPublicKey() with P521 returned incorrect SigningPublicKey length")
|
||||
}
|
||||
*/
|
280
lib/common/keys_and_cert.go
Normal file
280
lib/common/keys_and_cert.go
Normal file
@ -0,0 +1,280 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
I2P KeysAndCert
|
||||
https://geti2p.net/spec/common-structures#keysandcert
|
||||
Accurate for version 0.9.24
|
||||
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| public_key |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| padding (optional) |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| signing_key |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| certificate |
|
||||
+----+----+----+-//
|
||||
|
||||
public_key :: PublicKey (partial or full)
|
||||
length -> 256 bytes or as specified in key certificate
|
||||
|
||||
padding :: random data
|
||||
length -> 0 bytes or as specified in key certificate
|
||||
padding length + signing_key length == KEYS_AND_CERT_SPK_SIZE 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
|
||||
|
||||
certificate :: Certificate
|
||||
length -> >= 3 bytes
|
||||
|
||||
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 KeysAndCertInterface interface {
|
||||
GetPublicKey() (key crypto.PublicKey, err error)
|
||||
GetSigningPublicKey() (signing_public_key crypto.SigningPublicKey, err error)
|
||||
GetCertificate() (cert Certificate, err error)
|
||||
Bytes() (bytes []byte)
|
||||
}
|
||||
|
||||
type KeysAndCert struct {
|
||||
crypto.SigningPublicKey
|
||||
crypto.PublicKey
|
||||
CertificateInterface
|
||||
}
|
||||
|
||||
func (keys_and_cert KeysAndCert) Bytes() (bytes []byte) { //, err error) {
|
||||
pubkey, _ := keys_and_cert.GetPublicKey()
|
||||
signpubkey, _ := keys_and_cert.GetSigningPublicKey()
|
||||
elg_key := pubkey.(crypto.ElgPublicKey)
|
||||
dsa_key := signpubkey.(crypto.DSAPublicKey)
|
||||
bytes = append(bytes, dsa_key[:]...)
|
||||
bytes = append(bytes, elg_key[:]...)
|
||||
bytes = append(bytes, keys_and_cert.CertificateInterface.Cert()...)
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the PublicKey for this KeysAndCert, reading from the Key Certificate if it is present to
|
||||
// determine correct lengths.
|
||||
//
|
||||
func (keys_and_cert KeysAndCert) GetPublicKey() (key crypto.PublicKey, err error) {
|
||||
data := make([]byte, KEYS_AND_CERT_PUBKEY_SIZE)
|
||||
if keys_and_cert.PublicKey == nil {
|
||||
epk := crypto.ElgPublicKey{}
|
||||
copy(data[:KEYS_AND_CERT_PUBKEY_SIZE], epk[:])
|
||||
keys_and_cert.PublicKey = epk
|
||||
err = errors.New("error parsing KeysAndCert: data is smaller than minimum valid size")
|
||||
}
|
||||
/*cert, err := keys_and_cert.GetCertificate()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cert_len, err := cert.Length()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if cert_len != 0 {*/
|
||||
key = keys_and_cert.PublicKey
|
||||
/*}*/
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the SigningPublicKey for this KeysAndCert, reading from the Key Certificate if it is present to
|
||||
// determine correct lengths.
|
||||
//
|
||||
func (keys_and_cert KeysAndCert) GetSigningPublicKey() (signing_public_key crypto.SigningPublicKey, err error) {
|
||||
if keys_and_cert.SigningPublicKey == nil {
|
||||
keys_and_cert.SigningPublicKey = crypto.DSAPublicKey{}
|
||||
err = errors.New("error parsing KeysAndCert: data is smaller than minimum valid size")
|
||||
}
|
||||
/*cert, err := keys_and_cert.GetCertificate()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cert_len, err := cert.Length()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if cert_len != 0 {*/
|
||||
signing_public_key = keys_and_cert.SigningPublicKey
|
||||
/*}*/
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the Certificate contained in the KeysAndCert and any errors encountered while parsing the
|
||||
// KeysAndCert or Certificate.
|
||||
//
|
||||
func (keys_and_cert KeysAndCert) GetCertificate() (cert CertificateInterface, err error) {
|
||||
data_len := len(keys_and_cert.Bytes())
|
||||
log.Println("LEN IS", data_len, "KEYS_AND_CERT_MIN_SIZE", KEYS_AND_CERT_MIN_SIZE)
|
||||
if data_len < KEYS_AND_CERT_MIN_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "GetCertificate",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYS_AND_CERT_MIN_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing keys and cert")
|
||||
err = errors.New("certificate parsing warning: certificate data is shorter than specified by length")
|
||||
}
|
||||
/*if data_len > CERT_MIN_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "ReadKeysAndCert",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYS_AND_CERT_MIN_SIZE,
|
||||
"reason": "too much data",
|
||||
}).Error("error parsing keys and cert")
|
||||
err = errors.New("certificate parsing warning: certificate data is longer than specified by length")
|
||||
}*/
|
||||
cert = keys_and_cert.CertificateInterface
|
||||
return
|
||||
}
|
||||
|
||||
func ReadKeys(data []byte, cert CertificateInterface) (spk crypto.SigningPublicKey, pk crypto.PublicKey, remainder []byte, err error) {
|
||||
data_len := len(data)
|
||||
if data_len < KEYS_AND_CERT_MIN_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "ReadKeys",
|
||||
"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")
|
||||
return
|
||||
}
|
||||
if cert == nil {
|
||||
// No Certificate is present, return the KEYS_AND_CERT_PUBKEY_SIZE byte
|
||||
// PublicKey space as ElgPublicKey.
|
||||
var elg_key crypto.ElgPublicKey
|
||||
copy(data[:KEYS_AND_CERT_PUBKEY_SIZE], elg_key[:])
|
||||
pk = elg_key
|
||||
} else {
|
||||
// A Certificate is present in this KeysAndCert
|
||||
cert_type, cert_bytes, e := cert.Type()
|
||||
err = e
|
||||
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.
|
||||
cert_integer, _ := NewInteger(cert_bytes)
|
||||
pk, err = KeyCertificate{PKType: cert_integer}.ConstructPublicKey(
|
||||
data[:KEYS_AND_CERT_PUBKEY_SIZE],
|
||||
)
|
||||
} else {
|
||||
// Key Certificate is not present, return the KEYS_AND_CERT_PUBKEY_SIZE byte
|
||||
// PublicKey space as ElgPublicKey. No other Certificate
|
||||
// types are currently in use.
|
||||
var elg_key crypto.ElgPublicKey
|
||||
copy(data[:KEYS_AND_CERT_PUBKEY_SIZE], elg_key[:])
|
||||
pk = elg_key
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeysAndCert) PublicKey",
|
||||
"cert_type": cert_type,
|
||||
}).Warn("unused certificate type observed")
|
||||
}
|
||||
// }
|
||||
if data_len == 0 {
|
||||
// No Certificate is present, return the KEYS_AND_CERT_SPK_SIZE byte
|
||||
// SigningPublicKey space as legacy DSA SHA1 SigningPublicKey.
|
||||
var dsa_pk crypto.DSAPublicKey
|
||||
copy(dsa_pk[:], data[KEYS_AND_CERT_PUBKEY_SIZE:KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_SIZE])
|
||||
spk = dsa_pk
|
||||
} else {
|
||||
// A Certificate is present in this KeysAndCert
|
||||
cert_type, cert_bytes, e := cert.Type()
|
||||
err = e
|
||||
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.
|
||||
cert_integer, _ := NewInteger(cert_bytes)
|
||||
spk, err = KeyCertificate{SPKType: cert_integer}.ConstructSigningPublicKey(
|
||||
data[KEYS_AND_CERT_PUBKEY_SIZE : KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_SIZE],
|
||||
)
|
||||
} else {
|
||||
// Key Certificate is not present, return the KEYS_AND_CERT_SPK_SIZE byte
|
||||
// SigningPublicKey space as legacy SHA DSA1 SigningPublicKey.
|
||||
// No other Certificate types are currently in use.
|
||||
var dsa_pk crypto.DSAPublicKey
|
||||
copy(dsa_pk[:], data[KEYS_AND_CERT_PUBKEY_SIZE:KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_SIZE])
|
||||
spk = dsa_pk
|
||||
}
|
||||
}
|
||||
cert_len, e := cert.Length()
|
||||
err = e
|
||||
if cert_len == 0 {
|
||||
remainder = data[KEYS_AND_CERT_MIN_SIZE:]
|
||||
return
|
||||
}
|
||||
remainder = data[KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_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)
|
||||
keys_and_cert.CertificateInterface = &Certificate{}
|
||||
if data_len < KEYS_AND_CERT_MIN_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"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")
|
||||
return
|
||||
}
|
||||
cert, remainder, err := ReadCertificate(data[KEYS_AND_CERT_DATA_SIZE:])
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "ReadKeysAndCert",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYS_AND_CERT_MIN_SIZE,
|
||||
"reason": "error parsing certificate",
|
||||
}).Error("error parsing keys and cert")
|
||||
err = errors.New("error parsing KeysAndCert: error parsing certificate")
|
||||
return
|
||||
}
|
||||
keys_and_cert.CertificateInterface = cert
|
||||
spk, pk, remainder, err := ReadKeys(data, cert)
|
||||
keys_and_cert.SigningPublicKey = spk
|
||||
keys_and_cert.PublicKey = pk
|
||||
return
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
// 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
|
||||
)
|
||||
|
||||
/*
|
||||
[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 |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| padding (optional) |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| signing_key |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| certificate |
|
||||
+----+----+----+-//
|
||||
|
||||
public_key :: PublicKey (partial or full)
|
||||
length -> 256 bytes or as specified in key certificate
|
||||
|
||||
padding :: random data
|
||||
length -> 0 bytes or as specified in key certificate
|
||||
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 == 128 bytes
|
||||
|
||||
certificate :: Certificate
|
||||
length -> >= 3 bytes
|
||||
|
||||
total length: 387+ bytes
|
||||
*/
|
||||
|
||||
// KeysAndCert is the represenation of an I2P KeysAndCert.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
if cert_len == 0 {
|
||||
// No Certificate is present, return the KEYS_AND_CERT_PUBKEY_SIZE byte
|
||||
// PublicKey space as ElgPublicKey.
|
||||
var elg_key crypto.ElgPublicKey
|
||||
copy(keys_and_cert[:KEYS_AND_CERT_PUBKEY_SIZE], elg_key[:])
|
||||
key = elg_key
|
||||
} else {
|
||||
// A Certificate is present in this KeysAndCert
|
||||
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 = KeyCertificateFromCertificate(cert).ConstructPublicKey(
|
||||
keys_and_cert[:KEYS_AND_CERT_PUBKEY_SIZE],
|
||||
)
|
||||
} else {
|
||||
// Key Certificate is not present, return the KEYS_AND_CERT_PUBKEY_SIZE byte
|
||||
// PublicKey space as ElgPublicKey. No other Certificate
|
||||
// types are currently in use.
|
||||
var elg_key crypto.ElgPublicKey
|
||||
copy(keys_and_cert[:KEYS_AND_CERT_PUBKEY_SIZE], elg_key[:])
|
||||
key = elg_key
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(KeysAndCert) PublicKey",
|
||||
"cert_type": cert_type,
|
||||
}).Warn("unused certificate type observed")
|
||||
}
|
||||
|
||||
}
|
||||
return*/
|
||||
return keys_and_cert.publicKey
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if cert_len == 0 {
|
||||
// No Certificate is present, return the KEYS_AND_CERT_SPK_SIZE byte
|
||||
// SigningPublicKey space as legacy DSA SHA1 SigningPublicKey.
|
||||
var dsa_pk crypto.DSAPublicKey
|
||||
copy(dsa_pk[:], keys_and_cert[KEYS_AND_CERT_PUBKEY_SIZE:KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_SIZE])
|
||||
signing_public_key = dsa_pk
|
||||
} else {
|
||||
// A Certificate is present in this KeysAndCert
|
||||
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 = KeyCertificateFromCertificate(cert).ConstructSigningPublicKey(
|
||||
keys_and_cert[KEYS_AND_CERT_PUBKEY_SIZE : KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_SIZE],
|
||||
)
|
||||
} else {
|
||||
// Key Certificate is not present, return the KEYS_AND_CERT_SPK_SIZE byte
|
||||
// SigningPublicKey space as legacy SHA DSA1 SigningPublicKey.
|
||||
// No other Certificate types are currently in use.
|
||||
var dsa_pk crypto.DSAPublicKey
|
||||
copy(dsa_pk[:], keys_and_cert[KEYS_AND_CERT_PUBKEY_SIZE:KEYS_AND_CERT_PUBKEY_SIZE+KEYS_AND_CERT_SPK_SIZE])
|
||||
signing_public_key = dsa_pk
|
||||
}
|
||||
|
||||
}*/
|
||||
return keys_and_cert.signingPublicKey
|
||||
}
|
||||
|
||||
// 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": "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
|
||||
} else if data_len < KEYS_AND_CERT_DATA_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"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")
|
||||
return
|
||||
}
|
||||
keys_and_cert.KeyCertificate, remainder, err = NewKeyCertificate(data[KEYS_AND_CERT_DATA_SIZE:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
@ -1,232 +0,0 @@
|
||||
package keys_and_cert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
/*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...)
|
||||
_, _, err := NewKeysAndCert(data)
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
|
||||
}
|
||||
}*/
|
||||
|
||||
func TestCertificateWithValidData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
data := make([]byte, 128+256)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
assert.Nil(err)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyWithBadData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 193)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
pub_key := keys_and_cert.PublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
assert.Nil(pub_key)
|
||||
}
|
||||
|
||||
func TestPublicKeyWithBadCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
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())
|
||||
}
|
||||
assert.Nil(pub_key)
|
||||
}
|
||||
|
||||
func TestPublicKeyWithNullCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x00, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
pub_key := keys_and_cert.PublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(pub_key_data), pub_key.Len())
|
||||
}
|
||||
|
||||
func TestPublicKeyWithKeyCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
pub_key := keys_and_cert.PublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(pub_key_data), pub_key.Len())
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithBadData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 93)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
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())
|
||||
}
|
||||
assert.Nil(signing_pub_key)
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithBadCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
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())
|
||||
}
|
||||
assert.Nil(signing_pub_key)
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithNullCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x00, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
signing_pub_key_data := make([]byte, 128)
|
||||
data := append(pub_key_data, signing_pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
signing_pub_key := keys_and_cert.SigningPublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(signing_pub_key_data), signing_pub_key.Len())
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithKeyCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
signing_pub_key_data := make([]byte, 128)
|
||||
data := append(pub_key_data, signing_pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := NewKeysAndCert(data)
|
||||
|
||||
signing_pub_key := keys_and_cert.SigningPublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(signing_pub_key_data), signing_pub_key.Len())
|
||||
}
|
||||
|
||||
func TestNewKeysAndCertWithMissingData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := make([]byte, 128)
|
||||
_, 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())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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}...)
|
||||
_, 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())
|
||||
}
|
||||
}
|
||||
|
||||
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}...)
|
||||
_, remainder, err := NewKeysAndCert(cert_data)
|
||||
assert.Equal(0, len(remainder))
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestNewKeysAndCertWithValidDataWithoutCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := make([]byte, 128+256)
|
||||
cert_data = append(cert_data, []byte{0x00, 0x00, 0x00}...)
|
||||
_, remainder, err := NewKeysAndCert(cert_data)
|
||||
assert.Equal(0, len(remainder))
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
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}...)
|
||||
_, remainder, err := NewKeysAndCert(cert_data)
|
||||
if assert.Equal(1, len(remainder)) {
|
||||
assert.Equal("A", string(remainder[0]))
|
||||
}
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
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}...)
|
||||
_, remainder, err := NewKeysAndCert(cert_data)
|
||||
if assert.Equal(1, len(remainder)) {
|
||||
assert.Equal("A", string(remainder[0]))
|
||||
}
|
||||
assert.Nil(err)
|
||||
}
|
301
lib/common/keys_and_cert_test.go
Normal file
301
lib/common/keys_and_cert_test.go
Normal file
@ -0,0 +1,301 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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, remainder, err := ReadKeysAndCert(data)
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
t.Log("\n\nREMAINDER", remainder, "\n\n")
|
||||
cert, err := keys_and_cert.GetCertificate()
|
||||
t.Log("\n\nSTART\n\n")
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
|
||||
} else {
|
||||
t.Log("\n\nEND\n\n", cert.Cert())
|
||||
}
|
||||
// cert_bytes := []byte(cert.Cert())
|
||||
// if assert.Equal(len(cert_data), len(cert_bytes)) {
|
||||
// assert.Equal(cert_bytes, cert_data, "keys_and_cert.GetCertificate() did not return available data when cert was missing some data")
|
||||
// }
|
||||
}
|
||||
|
||||
func TestCertificateWithValidData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
data := make([]byte, 128+256)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
cert, err := keys_and_cert.GetCertificate()
|
||||
assert.Nil(err)
|
||||
cert_bytes := []byte(cert.Cert())
|
||||
if assert.Equal(len(cert_data), len(cert_bytes)) {
|
||||
assert.Equal(cert_bytes, cert_data, "keys_and_cert.GetCertificate() did not return correct data with valid cert")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyWithBadData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 193)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
//pub_key
|
||||
_, err = keys_and_cert.GetPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
//TODO: pub_key in this instance is a null key(all zeros). This test should be changed to check for this.
|
||||
//assert.Nil(pub_key)
|
||||
}
|
||||
|
||||
func TestPublicKeyWithBadCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
//pub_key
|
||||
_, err = keys_and_cert.GetPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
//TODO: pub_key in this instance is a null key(all zeros). This test should be changed to check for this.
|
||||
//assert.Nil(pub_key)
|
||||
}
|
||||
|
||||
func TestPublicKeyWithNullCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x00, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
pub_key, err := keys_and_cert.GetPublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(pub_key_data), pub_key.Len())
|
||||
}
|
||||
|
||||
func TestPublicKeyWithKeyCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
assert.Nil(err)
|
||||
|
||||
pub_key, err := keys_and_cert.GetPublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(pub_key_data), pub_key.Len())
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithBadData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 93)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
signing_pub_key, err := keys_and_cert.GetSigningPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
assert.Nil(signing_pub_key)
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithBadCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01}
|
||||
pub_key_data := make([]byte, 256)
|
||||
data := make([]byte, 128)
|
||||
data = append(data, pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
signing_pub_key, err := keys_and_cert.GetSigningPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
|
||||
}
|
||||
assert.Nil(signing_pub_key)
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithNullCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x00, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
signing_pub_key_data := make([]byte, 128)
|
||||
data := append(pub_key_data, signing_pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
signing_pub_key, err := keys_and_cert.GetSigningPublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(signing_pub_key_data), signing_pub_key.Len())
|
||||
}
|
||||
|
||||
func TestSigningPublicKeyWithKeyCertificate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}
|
||||
pub_key_data := make([]byte, 256)
|
||||
signing_pub_key_data := make([]byte, 128)
|
||||
data := append(pub_key_data, signing_pub_key_data...)
|
||||
data = append(data, cert_data...)
|
||||
keys_and_cert, _, err := ReadKeysAndCert(data)
|
||||
|
||||
signing_pub_key, err := keys_and_cert.GetSigningPublicKey()
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(signing_pub_key_data), signing_pub_key.Len())
|
||||
}
|
||||
|
||||
func TestReadKeysAndCertWithMissingData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cert_data := make([]byte, 128)
|
||||
keys_and_cert, remainder, err := ReadKeysAndCert(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.GetPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
_, err = keys_and_cert.GetSigningPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
_, err = keys_and_cert.GetCertificate()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("error parsing KeysAndCert: data is smaller than minimum valid size", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadKeysAndCertWithMissingCertData(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)
|
||||
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.GetPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
|
||||
}
|
||||
_, err = keys_and_cert.GetSigningPublicKey()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
|
||||
}
|
||||
_, err = keys_and_cert.GetCertificate()
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal("certificate parsing warning: certificate data is shorter than specified by length", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadKeysAndCertWithValidDataWithCertificate(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)
|
||||
assert.Equal(0, len(remainder))
|
||||
assert.Nil(err)
|
||||
|
||||
_, err = keys_and_cert.GetPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetPublicKey() returned error with valid data containing certificate")
|
||||
_, err = keys_and_cert.GetSigningPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetSigningPublicKey() returned error with valid data containing certificate")
|
||||
_, err = keys_and_cert.GetCertificate()
|
||||
assert.Nil(err, "keys_and_cert.GetCertificate() returned error with valid data containing certificate")
|
||||
}
|
||||
|
||||
func TestReadKeysAndCertWithValidDataWithoutCertificate(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)
|
||||
assert.Equal(0, len(remainder))
|
||||
assert.Nil(err)
|
||||
|
||||
_, err = keys_and_cert.GetPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetPublicKey() returned error with valid data not containing certificate")
|
||||
_, err = keys_and_cert.GetSigningPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetSigningPublicKey() returned error with valid data not containing certificate")
|
||||
_, err = keys_and_cert.GetCertificate()
|
||||
assert.Nil(err, "keys_and_cert.GetCertificate() returned error with valid data not containing certificate")
|
||||
}
|
||||
|
||||
func TestReadKeysAndCertWithValidDataWithCertificateAndRemainder(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)
|
||||
if assert.Equal(1, len(remainder)) {
|
||||
assert.Equal("A", string(remainder[0]))
|
||||
}
|
||||
assert.Nil(err)
|
||||
|
||||
_, err = keys_and_cert.GetPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetPublicKey() returned error with valid data containing certificate")
|
||||
_, err = keys_and_cert.GetSigningPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetSigningPublicKey() returned error with valid data containing certificate")
|
||||
_, err = keys_and_cert.GetCertificate()
|
||||
assert.Nil(err, "keys_and_cert.GetCertificate() returned error with valid data containing certificate")
|
||||
}
|
||||
|
||||
func TestReadKeysAndCertWithValidDataWithoutCertificateAndRemainder(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)
|
||||
if assert.Equal(1, len(remainder)) {
|
||||
assert.Equal("A", string(remainder[0]))
|
||||
}
|
||||
assert.Nil(err)
|
||||
|
||||
_, err = keys_and_cert.GetPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetPublicKey() returned error with valid data not containing certificate")
|
||||
_, err = keys_and_cert.GetSigningPublicKey()
|
||||
assert.Nil(err, "keys_and_cert.GetSigningPublicKey() returned error with valid data not containing certificate")
|
||||
_, err = keys_and_cert.GetCertificate()
|
||||
assert.Nil(err, "keys_and_cert.GetCertificate() returned error with valid data not containing certificate")
|
||||
}
|
108
lib/common/lease.go
Normal file
108
lib/common/lease.go
Normal file
@ -0,0 +1,108 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
/*
|
||||
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
|
||||
LEASE_TUNNEL_DATE_SIZE = 8
|
||||
)
|
||||
|
||||
type LeaseInterface interface {
|
||||
TunnelGateway() (hash Hash)
|
||||
TunnelID() uint32
|
||||
Date() (date Date)
|
||||
}
|
||||
|
||||
type Lease struct {
|
||||
LeaseHash Hash
|
||||
TunnelIdent *Integer
|
||||
TunnelDate Date
|
||||
} //[LEASE_SIZE]byte
|
||||
|
||||
var li LeaseInterface = &Lease{}
|
||||
|
||||
//
|
||||
// Return the first 32 bytes of the Lease as a Hash.
|
||||
//
|
||||
func (lease Lease) TunnelGateway() (hash Hash) {
|
||||
copy(hash[:], lease.LeaseHash[:])
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the TunnelID Integer in the Lease.
|
||||
//
|
||||
func (lease Lease) TunnelID() uint32 {
|
||||
return uint32(lease.TunnelIdent.Value())
|
||||
}
|
||||
|
||||
//
|
||||
// Return the Date inside the Lease.
|
||||
//
|
||||
func (lease Lease) Date() (date Date) {
|
||||
copy(date[:], lease.TunnelDate[:])
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Possibly temporary? Just to make it compile for now
|
||||
//
|
||||
func (lease Lease) Bytes() (bytes []byte) {
|
||||
var r []byte
|
||||
r = append(r, lease.LeaseHash[:]...)
|
||||
r = append(r, lease.TunnelIdent.Bytes()...)
|
||||
r = append(r, lease.TunnelDate[:]...)
|
||||
return r
|
||||
}
|
||||
|
||||
func ReadLease(data []byte) (lease Lease, remainder []byte, err error) {
|
||||
if len(data) < LEASE_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(Lease) ReadLease",
|
||||
"data_len": len(data),
|
||||
"required_len": "44",
|
||||
"reason": "lease missing data",
|
||||
}).Error("error parsnig lease")
|
||||
err = errors.New("error parsing lease: lease missing data")
|
||||
}
|
||||
lease.LeaseHash, remainder, err = ReadHash(data)
|
||||
identbytes, remainder, err := ReadIdent(remainder)
|
||||
lease.TunnelIdent, err = NewInteger(identbytes[:])
|
||||
lease.TunnelDate, remainder, err = ReadDate(remainder)
|
||||
return
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
// 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
|
||||
}
|
40
lib/common/lease2.go
Normal file
40
lib/common/lease2.go
Normal file
@ -0,0 +1,40 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
Lease2
|
||||
https://geti2p.net/spec/common-structures#lease2
|
||||
Description
|
||||
|
||||
Defines the authorization for a particular tunnel to receive messages targeting a Destination. Same as Lease but with a 4-byte end_date. Used by LeaseSet2. Supported as of 0.9.38; see proposal 123 for more information.
|
||||
Contents
|
||||
|
||||
SHA256 Hash of the RouterIdentity of the gateway router, then the TunnelId, and finally a 4 byte 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 :: 4 byte date
|
||||
length -> 4 bytes
|
||||
Seconds since the epoch, rolls over in 2106.
|
||||
|
||||
Notes
|
||||
|
||||
Total size: 40 bytes
|
||||
|
||||
JavaDoc: http://echelon.i2p/javadoc/net/i2p/data/Lease2.html
|
||||
*/
|
336
lib/common/lease_set.go
Normal file
336
lib/common/lease_set.go
Normal file
@ -0,0 +1,336 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
I2P LeaseSet
|
||||
https://geti2p.net/spec/common-structures#leaseset
|
||||
Accurate for version 0.9.24
|
||||
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| destination |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| encryption_key |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| signing_key |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|num | Lease 0 |
|
||||
+----+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Lease 1 |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Lease ($num-1) |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| signature |
|
||||
+ +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|
||||
destination :: Destination
|
||||
length -> >= 387 bytes
|
||||
|
||||
encryption_key :: PublicKey
|
||||
length -> 256 bytes
|
||||
|
||||
signing_key :: SigningPublicKey
|
||||
length -> 128 bytes or as specified in destination's key certificate
|
||||
|
||||
num :: Integer
|
||||
length -> 1 byte
|
||||
Number of leases to follow
|
||||
value: 0 <= num <= 16
|
||||
|
||||
leases :: [Lease]
|
||||
length -> $num*44 bytes
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
type LeaseSetInterface interface {
|
||||
GetPublicKey() (public_key crypto.ElgPublicKey, err error)
|
||||
GetSigningKey() (signing_public_key crypto.SigningPublicKey, err error)
|
||||
Leases() (leases []Lease, err error)
|
||||
/* LeaseCount() (count int, err error)*/
|
||||
|
||||
GetSignature() (signature Signature, err error)
|
||||
/* Verify() error
|
||||
NewestExpiration() (oldest Date, err error)
|
||||
OldestExpiration() (earliest Date, err error)*/
|
||||
}
|
||||
|
||||
type LeaseSet struct {
|
||||
Destination
|
||||
crypto.SigningPublicKey
|
||||
crypto.ElgPublicKey
|
||||
LeaseList []Lease
|
||||
}
|
||||
|
||||
var lsi LeaseSetInterface = &LeaseSet{}
|
||||
|
||||
//
|
||||
// Read a Destination from the LeaseSet.
|
||||
//
|
||||
func (lease_set LeaseSet) GetDestination() (destination Destination, err error) {
|
||||
if &lease_set.Destination != nil {
|
||||
destination = lease_set.Destination
|
||||
} else {
|
||||
err = errors.New("Error leaseset does not contain a destination")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the PublicKey in this LeaseSet and any errors ancountered parsing the LeaseSet.
|
||||
//
|
||||
func (lease_set LeaseSet) GetPublicKey() (public_key crypto.ElgPublicKey, err error) {
|
||||
public_key = lease_set.ElgPublicKey
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the SigningPublicKey, as specified in the LeaseSet's Destination's Key Certificate if
|
||||
// present, or a legacy DSA key.
|
||||
//
|
||||
func (lease_set LeaseSet) GetSigningKey() (signing_public_key crypto.SigningPublicKey, err error) {
|
||||
if lease_set.SigningPublicKey == nil {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) SigningKey",
|
||||
"public": lease_set.SigningPublicKey,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing signing public key")
|
||||
err = errors.New("error parsing signing public key: not enough data")
|
||||
return
|
||||
}
|
||||
signing_public_key = lease_set.SigningPublicKey
|
||||
return
|
||||
}
|
||||
|
||||
func (lease_set LeaseSet) Leases() (leases []Lease, err error) {
|
||||
leases = lease_set.LeaseList
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the number of Leases specified by the LeaseCount value in this LeaseSet.
|
||||
//
|
||||
func (lease_set LeaseSet) LeaseCount() (count int, err error) {
|
||||
count = len(lease_set.LeaseList)
|
||||
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.
|
||||
//
|
||||
func (lease_set LeaseSet) GetSignature() (signature Signature, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
/*
|
||||
func (lease_set LeaseSet) Verify() error {
|
||||
//data_end := len(destination) +
|
||||
// LEASE_SET_PUBKEY_SIZE +
|
||||
// LEASE_SET_SPK_SIZE +
|
||||
// 1 +
|
||||
// (44 * lease_set.LeaseCount())
|
||||
//data := lease_set[:data_end]
|
||||
//spk, _ := lease_set.
|
||||
// Destination().
|
||||
// SigningPublicKey()
|
||||
//verifier, err := spk.NewVerifier()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
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) {
|
||||
leases, err := lease_set.Leases()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
oldest = Date{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
for _, lease := range leases {
|
||||
date := lease.Date()
|
||||
if date.Time().After(oldest.Time()) {
|
||||
oldest = date
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Return the oldest date from all the Leases in the LeaseSet.
|
||||
//
|
||||
func (lease_set LeaseSet) OldestExpiration() (earliest Date, err error) {
|
||||
leases, err := lease_set.Leases()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
earliest = Date{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
for _, lease := range leases {
|
||||
date := lease.Date()
|
||||
if date.Time().Before(earliest.Time()) {
|
||||
earliest = date
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadLeaseSetSignature(bytes []byte, cert CertificateInterface) (signature Signature, remainder []byte, err error) {
|
||||
start := 0
|
||||
cert_type, _, _ := cert.Type()
|
||||
var end int
|
||||
if cert_type == CERT_KEY {
|
||||
end = start + cert.SignatureSize()
|
||||
} else {
|
||||
end = start + LEASE_SET_SIG_SIZE
|
||||
}
|
||||
bytes_len := len(bytes)
|
||||
if bytes_len < end {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) Signature",
|
||||
"data_len": bytes_len,
|
||||
"required_len": end,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing signatre")
|
||||
err = errors.New("error parsing signature: not enough data")
|
||||
signature = []byte(bytes[start:bytes_len])
|
||||
return
|
||||
}
|
||||
signature = []byte(bytes[start:end])
|
||||
return
|
||||
}
|
||||
|
||||
func ReadLeaseCount(bytes []byte) (count *Integer, err error) {
|
||||
remainder_len := len(bytes)
|
||||
if remainder_len < LEASE_SET_PUBKEY_SIZE+LEASE_SET_SPK_SIZE+1 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) LeaseCount",
|
||||
"data_len": remainder_len,
|
||||
"required_len": LEASE_SET_PUBKEY_SIZE + LEASE_SET_SPK_SIZE + 1,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing lease count")
|
||||
err = errors.New("error parsing lease count: not enough data")
|
||||
return
|
||||
}
|
||||
count, err = NewInteger([]byte{bytes[LEASE_SET_PUBKEY_SIZE+LEASE_SET_SPK_SIZE]})
|
||||
if count.Value() > 16 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) LeaseCount",
|
||||
"lease_count": count,
|
||||
"reason": "more than 16 leases",
|
||||
}).Warn("invalid lease set")
|
||||
err = errors.New("invalid lease set: more than 16 leases")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Read the Leases in this LeaseSet, returning a partial set if there is insufficient data.
|
||||
//
|
||||
func ReadLeases(bytes []byte) (leases []Lease, remainder []byte, err error) {
|
||||
count, err := ReadLeaseCount(bytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < count.Value(); i++ {
|
||||
start := 0 //offset + (i * LEASE_SIZE)
|
||||
end := start + LEASE_SIZE
|
||||
lease_set_len := len(bytes)
|
||||
if lease_set_len < end {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) Leases",
|
||||
"data_len": lease_set_len,
|
||||
"required_len": end,
|
||||
"reason": "some leases missing",
|
||||
}).Error("error parsnig lease set")
|
||||
err = errors.New("error parsing lease set: some leases missing")
|
||||
return
|
||||
}
|
||||
var lease Lease
|
||||
lease, remainder, err = ReadLease(bytes[start:end])
|
||||
leases = append(leases, lease)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadLeaseSetKeys(data []byte, cert CertificateInterface) (spk crypto.SigningPublicKey, pk crypto.ElgPublicKey, remainder []byte, err error) {
|
||||
spk, ppk, remainder, err := ReadKeys(data, cert)
|
||||
switch ppk.(type) {
|
||||
case crypto.ElgPublicKey:
|
||||
pk = ppk.(crypto.ElgPublicKey)
|
||||
default:
|
||||
err = errors.New("LeaseSet1 uses Elgamal public keys.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadLeaseSet(data []byte) (lease_set LeaseSet, remainder []byte, err error) {
|
||||
destination, remainder, err := ReadDestination(data)
|
||||
lease_set.Destination = destination
|
||||
//offset := len(destination.Bytes()) + LEASE_SET_PUBKEY_SIZE + LEASE_SET_SPK_SIZE + 1
|
||||
spk, pk, remainder, err := ReadLeaseSetKeys(remainder, nil)
|
||||
lease_set.SigningPublicKey = spk
|
||||
lease_set.ElgPublicKey = pk
|
||||
leases, remainder, err := ReadLeases(data)
|
||||
lease_set.LeaseList = leases
|
||||
|
||||
return
|
||||
}
|
@ -1,368 +0,0 @@
|
||||
// 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
|
||||
)
|
||||
|
||||
/*
|
||||
[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 |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| encryption_key |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| signing_key |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|num | Lease 0 |
|
||||
+----+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Lease 1 |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Lease ($num-1) |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| signature |
|
||||
+ +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+ +
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|
||||
destination :: Destination
|
||||
length -> >= 387 bytes
|
||||
|
||||
encryption_key :: PublicKey
|
||||
length -> 256 bytes
|
||||
|
||||
signing_key :: SigningPublicKey
|
||||
length -> 128 bytes or as specified in destination's key certificate
|
||||
|
||||
num :: Integer
|
||||
length -> 1 byte
|
||||
Number of leases to follow
|
||||
value: 0 <= num <= 16
|
||||
|
||||
leases :: [Lease]
|
||||
length -> $num*44 bytes
|
||||
|
||||
signature :: Signature
|
||||
length -> 40 bytes or as specified in destination's key certificate
|
||||
*/
|
||||
|
||||
// LeaseSet is the represenation of an I2P LeaseSet.
|
||||
//
|
||||
// https://geti2p.net/spec/common-structures#leaseset
|
||||
type LeaseSet []byte
|
||||
|
||||
/*
|
||||
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 := NewKeysAndCert(lease_set)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
destination, _, err = ReadDestination(keys_and_cert.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
// 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 := NewKeysAndCert(lease_set)
|
||||
remainder_len := len(remainder)
|
||||
if remainder_len < LEASE_SET_PUBKEY_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) PublicKey",
|
||||
"data_len": remainder_len,
|
||||
"required_len": LEASE_SET_PUBKEY_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing public key")
|
||||
err = errors.New("error parsing public key: not enough data")
|
||||
copy(public_key[:], remainder)
|
||||
return
|
||||
}
|
||||
copy(public_key[:], remainder[:LEASE_SET_PUBKEY_SIZE])
|
||||
return
|
||||
}
|
||||
|
||||
// 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.Bytes()) + LEASE_SET_PUBKEY_SIZE
|
||||
cert := destination.Certificate()
|
||||
cert_len := cert.Length()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lease_set_len := len(lease_set)
|
||||
if lease_set_len < offset+LEASE_SET_SPK_SIZE {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) SigningKey",
|
||||
"data_len": lease_set_len,
|
||||
"required_len": offset + LEASE_SET_SPK_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing signing public key")
|
||||
err = errors.New("error parsing signing public key: not enough data")
|
||||
return
|
||||
}
|
||||
if cert_len == 0 {
|
||||
// No Certificate is present, return the LEASE_SET_SPK_SIZE byte
|
||||
// SigningPublicKey space as legacy DSA SHA1 SigningPublicKey.
|
||||
var dsa_pk crypto.DSAPublicKey
|
||||
copy(dsa_pk[:], lease_set[offset:offset+LEASE_SET_SPK_SIZE])
|
||||
signing_public_key = dsa_pk
|
||||
} else {
|
||||
// A Certificate is present in this LeaseSet's Destination
|
||||
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 = KeyCertificateFromCertificate(cert).ConstructSigningPublicKey(
|
||||
lease_set[offset : offset+LEASE_SET_SPK_SIZE],
|
||||
)
|
||||
} else {
|
||||
// No Certificate is present, return the LEASE_SET_SPK_SIZE byte
|
||||
// SigningPublicKey space as legacy DSA SHA1 SigningPublicKey.
|
||||
var dsa_pk crypto.DSAPublicKey
|
||||
copy(dsa_pk[:], lease_set[offset:offset+LEASE_SET_SPK_SIZE])
|
||||
signing_public_key = dsa_pk
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 := NewKeysAndCert(lease_set)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
remainder_len := len(remainder)
|
||||
if remainder_len < LEASE_SET_PUBKEY_SIZE+LEASE_SET_SPK_SIZE+1 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) LeaseCount",
|
||||
"data_len": remainder_len,
|
||||
"required_len": LEASE_SET_PUBKEY_SIZE + LEASE_SET_SPK_SIZE + 1,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing lease count")
|
||||
err = errors.New("error parsing lease count: not enough data")
|
||||
return
|
||||
}
|
||||
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",
|
||||
"lease_count": count,
|
||||
"reason": "more than 16 leases",
|
||||
}).Warn("invalid lease set")
|
||||
err = errors.New("invalid lease set: more than 16 leases")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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.Bytes()) + LEASE_SET_PUBKEY_SIZE + LEASE_SET_SPK_SIZE + 1
|
||||
count, err := lease_set.LeaseCount()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
start := offset + (i * LEASE_SIZE)
|
||||
end := start + LEASE_SIZE
|
||||
lease_set_len := len(lease_set)
|
||||
if lease_set_len < end {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) Leases",
|
||||
"data_len": lease_set_len,
|
||||
"required_len": end,
|
||||
"reason": "some leases missing",
|
||||
}).Error("error parsnig lease set")
|
||||
err = errors.New("error parsing lease set: some leases missing")
|
||||
return
|
||||
}
|
||||
var lease Lease
|
||||
copy(lease[:], lease_set[start:end])
|
||||
leases = append(leases, lease)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
lease_count, err := lease_set.LeaseCount()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
start := len(destination.Bytes()) +
|
||||
LEASE_SET_PUBKEY_SIZE +
|
||||
LEASE_SET_SPK_SIZE +
|
||||
1 +
|
||||
(LEASE_SIZE * lease_count)
|
||||
cert := destination.Certificate()
|
||||
cert_type := cert.Type()
|
||||
var end int
|
||||
if cert_type == CERT_KEY {
|
||||
end = start + KeyCertificateFromCertificate(cert).SignatureSize()
|
||||
} else {
|
||||
end = start + LEASE_SET_SIG_SIZE
|
||||
}
|
||||
lease_set_len := len(lease_set)
|
||||
if lease_set_len < end {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "(LeaseSet) Signature",
|
||||
"data_len": lease_set_len,
|
||||
"required_len": end,
|
||||
"reason": "not enough data",
|
||||
}).Error("error parsing signatre")
|
||||
err = errors.New("error parsing signature: not enough data")
|
||||
return
|
||||
}
|
||||
signature = []byte(lease_set[start:end])
|
||||
return
|
||||
}
|
||||
|
||||
// Verify returns nil
|
||||
func (lease_set LeaseSet) Verify() error {
|
||||
//data_end := len(destination) +
|
||||
// LEASE_SET_PUBKEY_SIZE +
|
||||
// LEASE_SET_SPK_SIZE +
|
||||
// 1 +
|
||||
// (44 * lease_set.LeaseCount())
|
||||
//data := lease_set[:data_end]
|
||||
//spk, _ := lease_set.
|
||||
// Destination().
|
||||
// SigningPublicKey()
|
||||
//verifier, err := spk.NewVerifier()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
return nil // verifier.Verify(data, lease_set.Signature())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
newest = Date{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
for _, lease := range leases {
|
||||
date := lease.Date()
|
||||
if date.Time().After(newest.Time()) {
|
||||
newest = date
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
earliest = Date{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
for _, lease := range leases {
|
||||
date := lease.Date()
|
||||
if date.Time().Before(earliest.Time()) {
|
||||
earliest = date
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
105
lib/common/lease_set_2.go
Normal file
105
lib/common/lease_set_2.go
Normal file
@ -0,0 +1,105 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
|
||||
https://geti2p.net/spec/common-structures#leaseset2
|
||||
LeaseSet2
|
||||
Description
|
||||
|
||||
Contained in a I2NP DatabaseStore message of type 3. Supported as of 0.9.38; see proposal 123 for more information.
|
||||
|
||||
Contains all of the currently authorized Lease2 for a particular Destination, and the PublicKey to which garlic messages can be encrypted. A LeaseSet is one of the two structures stored in the network database (the other being RouterInfo), and is keyed under the SHA256 of the contained Destination.
|
||||
Contents
|
||||
|
||||
LeaseSet2Header, followed by a options, then one or more PublicKey for encryption, Integer specifying how many Lease2 structures are in the set, followed by the actual Lease2 structures and finally a Signature of the previous bytes signed by the Destination's SigningPrivateKey or the transient key.
|
||||
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| ls2_header |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| options |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|numk| keytype0| keylen0 | |
|
||||
+----+----+----+----+----+ +
|
||||
| encryption_key_0 |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| keytypen| keylenn | |
|
||||
+----+----+----+----+ +
|
||||
| encryption_key_n |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| num| Lease2 0 |
|
||||
+----+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Lease2($num-1) |
|
||||
+ +
|
||||
| |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| signature |
|
||||
~ ~
|
||||
~ ~
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|
||||
ls2header :: LeaseSet2Header
|
||||
length -> varies
|
||||
|
||||
options :: Mapping
|
||||
length -> varies, 2 bytes minimum
|
||||
|
||||
numk :: Integer
|
||||
length -> 1 byte
|
||||
Number of key types, key lengths, and PublicKeys to follow
|
||||
value: 1 <= numk <= max TBD
|
||||
|
||||
keytype :: The encryption type of the PublicKey to follow.
|
||||
length -> 2 bytes
|
||||
|
||||
keylen :: The length of the PublicKey to follow.
|
||||
Must match the specified length of the encryption type.
|
||||
length -> 2 bytes
|
||||
|
||||
encryption_key :: PublicKey
|
||||
length -> 256 bytes
|
||||
|
||||
num :: Integer
|
||||
length -> 1 byte
|
||||
Number of Lease2s to follow
|
||||
value: 0 <= num <= 16
|
||||
|
||||
leases :: [Lease2]
|
||||
length -> $num*40 bytes
|
||||
|
||||
signature :: Signature
|
||||
length -> 40 bytes or as specified in destination's key
|
||||
certificate, or by the sigtype of the transient public key,
|
||||
if present in the header
|
||||
|
||||
Notes
|
||||
|
||||
The public key of the destination was used for the old I2CP-to-I2CP encryption which was disabled in version 0.6, it is currently unused.
|
||||
The encryption keys are used for end-to-end ElGamal/AES+SessionTag encryption [ELGAMAL-AES] (type 0) or other end-to-end encryption schemes. See [ECIES] and proposals 145 and 156. They may be generated anew at every router startup or they may be persistent. X25519 (type 4, see [ECIES]) is supported as of release 0.9.44.
|
||||
The signature is over the data above, PREPENDED with the single byte containing the DatabaseStore type (3).
|
||||
The signature may be verified using the signing public key of the destination, or the transient signing public key, if an offline signature is included in the leaseset2 header.
|
||||
The key length is provided for each key, so that floodfills and clients may parse the structure even if not all encryption types are known or supported.
|
||||
|
||||
JavaDoc: http://echelon.i2p/javadoc/net/i2p/data/LeaseSet2.html
|
||||
|
||||
*/
|
@ -1,22 +1,16 @@
|
||||
package lease_set
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func buildDestination() *router_identity.RouterIdentity {
|
||||
func buildDestination() RouterIdentity {
|
||||
router_ident_data := make([]byte, 128+256)
|
||||
router_ident_data = append(router_ident_data, []byte{0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}...)
|
||||
ident, _, err := router_identity.NewRouterIdentity(router_ident_data)
|
||||
panic(err)
|
||||
return ident
|
||||
rri, _, _ := ReadRouterIdentity(router_ident_data)
|
||||
return rri
|
||||
}
|
||||
|
||||
func buildPublicKey() []byte {
|
||||
@ -38,15 +32,15 @@ func buildSigningKey() []byte {
|
||||
func buildLease(n int) []byte {
|
||||
data := make([]byte, 0)
|
||||
for i := 0; i < n; i++ {
|
||||
l := make([]byte, lease.LEASE_SIZE)
|
||||
for p := range l {
|
||||
l[p] = byte(i)
|
||||
lease := make([]byte, LEASE_SIZE)
|
||||
for p := range lease {
|
||||
lease[p] = byte(i)
|
||||
}
|
||||
for q := lease.LEASE_SIZE - 9; q < lease.LEASE_SIZE-1; q++ {
|
||||
l[q] = 0x00
|
||||
for q := LEASE_SIZE - 9; q < LEASE_SIZE-1; q++ {
|
||||
lease[q] = 0x00
|
||||
}
|
||||
l[lease.LEASE_SIZE-1] = byte(i + 10)
|
||||
data = append(data, l...)
|
||||
lease[LEASE_SIZE-1] = byte(i + 10)
|
||||
data = append(data, lease...)
|
||||
}
|
||||
return data
|
||||
}
|
||||
@ -61,33 +55,34 @@ func buildSignature(size int) []byte {
|
||||
|
||||
func buildFullLeaseSet(n int) LeaseSet {
|
||||
lease_set_data := make([]byte, 0)
|
||||
lease_set_data = append(lease_set_data, buildDestination().KeysAndCert.KeyCertificate.RawBytes()...)
|
||||
lease_set_data = append(lease_set_data, buildDestination().Bytes()...)
|
||||
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))
|
||||
lease_set_data = append(lease_set_data, buildLease(n)...)
|
||||
lease_set_data = append(lease_set_data, buildSignature(64)...)
|
||||
return LeaseSet(lease_set_data)
|
||||
leaseSet, _, _ := ReadLeaseSet(lease_set_data)
|
||||
return leaseSet
|
||||
}
|
||||
|
||||
func TestDestinationIsCorrect(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
lease_set := buildFullLeaseSet(1)
|
||||
dest, err := lease_set.Destination()
|
||||
dest, err := lease_set.GetDestination()
|
||||
assert.Nil(err)
|
||||
dest_cert := dest.Certificate()
|
||||
//assert.Nil(err)
|
||||
cert_type := dest_cert.Type()
|
||||
dest_cert, err := dest.Certificate()
|
||||
assert.Nil(err)
|
||||
assert.Equal(certificate.CERT_KEY, cert_type)
|
||||
_, cert_bytes, err := dest_cert.Type()
|
||||
assert.Nil(err)
|
||||
assert.Equal(CERT_KEY, cert_bytes)
|
||||
}
|
||||
|
||||
func TestPublicKeyIsCorrect(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
lease_set := buildFullLeaseSet(1)
|
||||
pk, err := lease_set.PublicKey()
|
||||
pk, err := lease_set.GetPublicKey()
|
||||
if assert.Nil(err) {
|
||||
assert.Equal(
|
||||
0,
|
||||
@ -103,7 +98,7 @@ func TestSigningKeyIsCorrect(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
lease_set := buildFullLeaseSet(1)
|
||||
sk, err := lease_set.SigningKey()
|
||||
sk, err := lease_set.GetSigningKey()
|
||||
if assert.Nil(err) {
|
||||
assert.Equal(128, sk.Len())
|
||||
}
|
||||
@ -149,19 +144,19 @@ func TestLeasesHaveCorrectData(t *testing.T) {
|
||||
leases, err := lease_set.Leases()
|
||||
if assert.Nil(err) {
|
||||
for i := 0; i < count; i++ {
|
||||
l := make([]byte, lease.LEASE_SIZE)
|
||||
for p := range l {
|
||||
l[p] = byte(i)
|
||||
lease := make([]byte, LEASE_SIZE)
|
||||
for p := range lease {
|
||||
lease[p] = byte(i)
|
||||
}
|
||||
for q := lease.LEASE_SIZE - 9; q < lease.LEASE_SIZE-1; q++ {
|
||||
l[q] = 0x00
|
||||
for q := LEASE_SIZE - 9; q < LEASE_SIZE-1; q++ {
|
||||
lease[q] = 0x00
|
||||
}
|
||||
l[lease.LEASE_SIZE-1] = byte(i + 10)
|
||||
lease[LEASE_SIZE-1] = byte(i + 10)
|
||||
assert.Equal(
|
||||
0,
|
||||
bytes.Compare(
|
||||
l,
|
||||
leases[i][:],
|
||||
lease,
|
||||
leases[i].Bytes()[:],
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -173,7 +168,7 @@ func TestSignatureIsCorrect(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
lease_set := buildFullLeaseSet(1)
|
||||
sig, err := lease_set.Signature()
|
||||
sig, err := lease_set.GetSignature()
|
||||
if assert.Nil(err) {
|
||||
assert.Equal(
|
||||
0,
|
||||
@ -191,9 +186,8 @@ 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,
|
||||
Date{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, byte(4 + 10)},
|
||||
latest,
|
||||
)
|
||||
}
|
||||
@ -204,9 +198,8 @@ 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,
|
||||
Date{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a},
|
||||
latest,
|
||||
)
|
||||
}
|
228
lib/common/mapping.go
Normal file
228
lib/common/mapping.go
Normal file
@ -0,0 +1,228 @@
|
||||
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, err := NewInteger(remainder[:2])
|
||||
inferred_length := length.Value() + 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
|
||||
}
|
187
lib/common/mapping_test.go
Normal file
187
lib/common/mapping_test.go
Normal file
@ -0,0 +1,187 @@
|
||||
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")
|
||||
}
|
161
lib/common/router_address.go
Normal file
161
lib/common/router_address.go
Normal file
@ -0,0 +1,161 @@
|
||||
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 *Integer, err error) {
|
||||
err, exit := router_address.checkValid()
|
||||
if exit {
|
||||
return
|
||||
}
|
||||
cost, err = NewInteger([]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 := &Integer{}
|
||||
mapping := make([]byte, 0)
|
||||
if len(remainder) >= 2 {
|
||||
map_size, err = NewInteger(remainder[:2])
|
||||
if len(remainder) < map_size.Value()+2 {
|
||||
err = errors.New("not enough data for map inside router address")
|
||||
router_address = RouterAddress([]byte{})
|
||||
remainder = []byte{}
|
||||
return
|
||||
}
|
||||
mapping = remainder[:map_size.Value()+2]
|
||||
router_address = append(router_address, mapping...)
|
||||
}
|
||||
|
||||
remainder = data[ROUTER_ADDRESS_MIN_SIZE+len(str)+len(mapping):]
|
||||
return
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,47 +1,41 @@
|
||||
package router_address
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
. "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckValidReportsEmptySlice(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
router_address, _, err := ReadRouterAddress([]byte{})
|
||||
router_address := RouterAddress([]byte{})
|
||||
err, exit := router_address.checkValid()
|
||||
|
||||
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, _, err := ReadRouterAddress([]byte{0x01})
|
||||
router_address := RouterAddress([]byte{0x01})
|
||||
err, exit := router_address.checkValid()
|
||||
|
||||
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, _, _ := 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...)
|
||||
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...)
|
||||
err, exit := router_address.checkValid()
|
||||
|
||||
assert.Nil(err, "checkValid() reported error with valid data")
|
||||
@ -51,8 +45,8 @@ func TestCheckRouterAddressValidNoErrWithValidData(t *testing.T) {
|
||||
func TestRouterAddressCostReturnsFirstByte(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
router_address, _, err := ReadRouterAddress([]byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
|
||||
cost := router_address.Cost()
|
||||
router_address := RouterAddress([]byte{0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
|
||||
cost, err := router_address.Cost()
|
||||
|
||||
assert.Nil(err, "Cost() returned error with valid data")
|
||||
assert.Equal(cost, 6, "Cost() returned wrong cost")
|
||||
@ -61,8 +55,8 @@ func TestRouterAddressCostReturnsFirstByte(t *testing.T) {
|
||||
func TestRouterAddressExpirationReturnsCorrectData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
router_address, _, err := ReadRouterAddress([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00})
|
||||
expiration := router_address.Expiration()
|
||||
router_address := RouterAddress([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00})
|
||||
expiration, err := 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 {
|
||||
@ -77,7 +71,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.Data()...)
|
||||
router_address_bytes = append(router_address_bytes, mapping...)
|
||||
router_address_bytes = append(router_address_bytes, []byte{0x01, 0x02, 0x03}...)
|
||||
router_address, remainder, err := ReadRouterAddress(router_address_bytes)
|
||||
|
41
lib/common/router_identity.go
Normal file
41
lib/common/router_identity.go
Normal file
@ -0,0 +1,41 @@
|
||||
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 struct {
|
||||
KeysAndCert
|
||||
}
|
||||
|
||||
func (router_identity RouterIdentity) PublicKey() (crypto.PublicKey, error) {
|
||||
return router_identity.PublicKey()
|
||||
}
|
||||
|
||||
func (router_identity RouterIdentity) SigningPublicKey() (crypto.SigningPublicKey, error) {
|
||||
return router_identity.SigningPublicKey()
|
||||
}
|
||||
|
||||
func (router_identity RouterIdentity) Certificate() (Certificate, error) {
|
||||
return router_identity.Certificate()
|
||||
}
|
||||
|
||||
func ReadRouterIdentity(data []byte) (router_identity RouterIdentity, remainder []byte, err error) {
|
||||
keys_and_cert, remainder, err := ReadKeysAndCert(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
router_identity.KeysAndCert = keys_and_cert
|
||||
return
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
// 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
|
||||
}
|
268
lib/common/router_info.go
Normal file
268
lib/common/router_info.go
Normal file
@ -0,0 +1,268 @@
|
||||
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.Bytes())
|
||||
}
|
||||
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 *Integer, 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, err = NewInteger([]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.Value(); 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().Value()
|
||||
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().Value()
|
||||
ident, _ := router_info.RouterIdentity()
|
||||
keyCert := ident.CertificateInterface //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.Bytes())
|
||||
|
||||
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.Value(); 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 *Integer) {
|
||||
head := router_info.optionsLocation()
|
||||
size, _ = NewInteger(router_info[head : head+2]) //+ 2
|
||||
return
|
||||
}
|
@ -1,379 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
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()),
|
||||
),
|
||||
)
|
||||
}
|
195
lib/common/router_info_test.go
Normal file
195
lib/common/router_info_test.go
Normal file
@ -0,0 +1,195 @@
|
||||
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}...)
|
||||
b, _, err := ReadRouterIdentity(router_ident_data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
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().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")...)
|
||||
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().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")...)
|
||||
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().Bytes()),
|
||||
[]byte(router_identity.Bytes()),
|
||||
),
|
||||
)
|
||||
}
|
3
lib/common/session_key.go
Normal file
3
lib/common/session_key.go
Normal file
@ -0,0 +1,3 @@
|
||||
package common
|
||||
|
||||
type SessionKey [32]byte
|
@ -1,34 +0,0 @@
|
||||
// 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
|
||||
}
|
3
lib/common/session_tag.go
Normal file
3
lib/common/session_tag.go
Normal file
@ -0,0 +1,3 @@
|
||||
package common
|
||||
|
||||
type SessionTag [32]byte
|
@ -1,34 +0,0 @@
|
||||
// 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
|
||||
}
|
3
lib/common/signature.go
Normal file
3
lib/common/signature.go
Normal file
@ -0,0 +1,3 @@
|
||||
package common
|
||||
|
||||
type Signature []byte
|
@ -1,49 +0,0 @@
|
||||
// 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
|
||||
}
|
115
lib/common/string.go
Normal file
115
lib/common/string.go
Normal file
@ -0,0 +1,115 @@
|
||||
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 *Integer, 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, err = NewInteger([]byte{byte(str[0])})
|
||||
inferred_len := length.Value() + 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.Value()+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.Value()+1])
|
||||
remainder = data[length.Value()+1:]
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
@ -1,24 +1,23 @@
|
||||
package data
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringReportsCorrectLength(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
str_len, err := I2PString([]byte{0x02, 0x00, 0x00}).Length()
|
||||
str_len, err := String([]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 TestI2PStringReportsLengthZeroError(t *testing.T) {
|
||||
func TestStringReportsLengthZeroError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
str_len, err := I2PString(make([]byte, 0)).Length()
|
||||
str_len, err := String(make([]byte, 0)).Length()
|
||||
|
||||
assert.Equal(str_len, 0, "Length() reported non-zero length on empty slice")
|
||||
if assert.NotNil(err) {
|
||||
@ -26,10 +25,10 @@ func TestI2PStringReportsLengthZeroError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestI2PStringReportsExtraDataError(t *testing.T) {
|
||||
func TestStringReportsExtraDataError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
str_len, err := I2PString([]byte{0x01, 0x00, 0x00}).Length()
|
||||
str_len, err := String([]byte{0x01, 0x00, 0x00}).Length()
|
||||
|
||||
assert.Equal(str_len, 1, "Length() reported wrong size when extra data present")
|
||||
if assert.NotNil(err) {
|
||||
@ -37,10 +36,10 @@ func TestI2PStringReportsExtraDataError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestI2PStringDataReportsLengthZeroError(t *testing.T) {
|
||||
func TestStringDataReportsLengthZeroError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
str_len, err := I2PString([]byte{0x01}).Length()
|
||||
str_len, err := String([]byte{0x01}).Length()
|
||||
|
||||
assert.Equal(str_len, 1, "Length() reported wrong size with missing data")
|
||||
if assert.NotNil(err) {
|
||||
@ -48,10 +47,10 @@ func TestI2PStringDataReportsLengthZeroError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestI2PStringDataReportsExtraDataError(t *testing.T) {
|
||||
func TestStringDataReportsExtraDataError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
data, err := I2PString([]byte{0x01, 0x00, 0x01}).Data()
|
||||
data, err := String([]byte{0x01, 0x00, 0x01}).Data()
|
||||
data_len := len(data)
|
||||
|
||||
assert.Equal(data_len, 1, "Data() reported wrong size on string with extra data")
|
||||
@ -60,10 +59,10 @@ func TestI2PStringDataReportsExtraDataError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestI2PStringDataEmptyWhenZeroLength(t *testing.T) {
|
||||
func TestStringDataEmptyWhenZeroLength(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
data, err := I2PString(make([]byte, 0)).Data()
|
||||
data, err := String(make([]byte, 0)).Data()
|
||||
|
||||
assert.Equal(len(data), 0, "Data() returned data when none was present:")
|
||||
if assert.NotNil(err) {
|
||||
@ -71,10 +70,10 @@ func TestI2PStringDataEmptyWhenZeroLength(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestI2PStringDataErrorWhenNonZeroLengthOnly(t *testing.T) {
|
||||
func TestStringDataErrorWhenNonZeroLengthOnly(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
data, err := I2PString([]byte{0x01}).Data()
|
||||
data, err := String([]byte{0x01}).Data()
|
||||
|
||||
assert.Equal(len(data), 0, "Data() returned data when only length was present")
|
||||
if assert.NotNil(err) {
|
||||
@ -82,7 +81,7 @@ func TestI2PStringDataErrorWhenNonZeroLengthOnly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestToI2PI2PStringFormatsCorrectly(t *testing.T) {
|
||||
func TestToI2PStringFormatsCorrectly(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
i2p_string, err := ToI2PString(string([]byte{0x08, 0x09}))
|
||||
@ -112,38 +111,38 @@ func TestReadStringReadsLength(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := []byte{0x01, 0x04, 0x06}
|
||||
str, remainder, err := ReadI2PString(bytes)
|
||||
str, remainder, err := ReadString(bytes)
|
||||
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
||||
func TestReadI2PStringErrWhenEmptySlice(t *testing.T) {
|
||||
func TestReadStringErrWhenEmptySlice(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
bytes := make([]byte, 0)
|
||||
_, _, err := ReadI2PString(bytes)
|
||||
_, _, err := ReadString(bytes)
|
||||
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal(err.Error(), "error parsing string: zero length", "correct error message should be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadI2PStringErrWhenDataTooShort(t *testing.T) {
|
||||
func TestReadStringErrWhenDataTooShort(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
short_str := []byte{0x03, 0x01}
|
||||
str, remainder, err := ReadI2PString(short_str)
|
||||
str, remainder, err := ReadString(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, "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")
|
||||
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")
|
||||
}
|
583
lib/config/su3.go
Normal file
583
lib/config/su3.go
Normal file
@ -0,0 +1,583 @@
|
||||
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
|
||||
}
|
458
lib/config/su3_test.go
Normal file
458
lib/config/su3_test.go
Normal file
@ -0,0 +1,458 @@
|
||||
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)
|
||||
}
|
@ -1,2 +1,4 @@
|
||||
//
|
||||
// package for i2p specific crpytography
|
||||
//
|
||||
package crypto
|
||||
|
@ -1,114 +1,54 @@
|
||||
package crypto
|
||||
|
||||
/*
|
||||
#cgo pkg-config: libsodium
|
||||
#include <sodium.h>
|
||||
#include <stdint.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var Ed25519EncryptTooBig = errors.New("failed to encrypt data, too big for Ed25519")
|
||||
|
||||
type Ed25519PublicKey []byte
|
||||
type Ed25519PublicKey [32]byte
|
||||
|
||||
type Ed25519Verifier struct {
|
||||
k []byte
|
||||
k [32]C.uchar
|
||||
}
|
||||
|
||||
func (k Ed25519PublicKey) NewVerifier() (v Verifier, err error) {
|
||||
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
|
||||
ev := new(Ed25519Verifier)
|
||||
for i, b := range k {
|
||||
ev.k[i] = C.uchar(b)
|
||||
}
|
||||
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)
|
||||
v = ev
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Ed25519Verifier) VerifyHash(h, sig []byte) (err error) {
|
||||
if len(sig) != ed25519.SignatureSize {
|
||||
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
|
||||
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
|
||||
}
|
||||
@ -119,23 +59,35 @@ func (v *Ed25519Verifier) Verify(data, sig []byte) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type Ed25519PrivateKey ed25519.PrivateKey
|
||||
type Ed25519PrivateKey [32]byte
|
||||
|
||||
type Ed25519Signer struct {
|
||||
k []byte
|
||||
k [32]C.uchar
|
||||
}
|
||||
|
||||
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) {
|
||||
sig = ed25519.Sign(s.k, h)
|
||||
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)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -1,42 +1,9 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,9 @@ 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{
|
||||
|
@ -23,7 +23,9 @@ 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))
|
||||
|
@ -2,12 +2,10 @@ package i2np
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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/common"
|
||||
"github.com/go-i2p/go-i2p/lib/tunnel"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -157,9 +155,9 @@ type BuildRequestRecord struct {
|
||||
OurIdent common.Hash
|
||||
NextTunnel tunnel.TunnelID
|
||||
NextIdent common.Hash
|
||||
LayerKey session_key.SessionKey
|
||||
IVKey session_key.SessionKey
|
||||
ReplyKey session_key.SessionKey
|
||||
LayerKey common.SessionKey
|
||||
IVKey common.SessionKey
|
||||
ReplyKey common.SessionKey
|
||||
ReplyIV [16]byte
|
||||
Flag int
|
||||
RequestTime time.Time
|
||||
@ -253,7 +251,7 @@ func readBuildRequestRecordReceiveTunnel(data []byte) (tunnel.TunnelID, error) {
|
||||
}
|
||||
|
||||
receive_tunnel := tunnel.TunnelID(
|
||||
common.Integer(data[0:4]).Int(),
|
||||
common.Integer(data[0:4]),
|
||||
)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -283,7 +281,7 @@ func readBuildRequestRecordNextTunnel(data []byte) (tunnel.TunnelID, error) {
|
||||
}
|
||||
|
||||
next_tunnel := tunnel.TunnelID(
|
||||
common.Integer(data[36:40]).Int(),
|
||||
common.Integer(data[36:40]),
|
||||
)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -307,12 +305,12 @@ func readBuildRequestRecordNextIdent(data []byte) (common.Hash, error) {
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func readBuildRequestRecordLayerKey(data []byte) (session_key.SessionKey, error) {
|
||||
func readBuildRequestRecordLayerKey(data []byte) (common.SessionKey, error) {
|
||||
if len(data) < 104 {
|
||||
return session_key.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
return common.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
session_key := session_key.SessionKey{}
|
||||
session_key := common.SessionKey{}
|
||||
copy(session_key[:], data[72:104])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -321,12 +319,12 @@ func readBuildRequestRecordLayerKey(data []byte) (session_key.SessionKey, error)
|
||||
return session_key, nil
|
||||
}
|
||||
|
||||
func readBuildRequestRecordIVKey(data []byte) (session_key.SessionKey, error) {
|
||||
func readBuildRequestRecordIVKey(data []byte) (common.SessionKey, error) {
|
||||
if len(data) < 136 {
|
||||
return session_key.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
return common.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
session_key := session_key.SessionKey{}
|
||||
session_key := common.SessionKey{}
|
||||
copy(session_key[:], data[104:136])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -335,12 +333,12 @@ func readBuildRequestRecordIVKey(data []byte) (session_key.SessionKey, error) {
|
||||
return session_key, nil
|
||||
}
|
||||
|
||||
func readBuildRequestRecordReplyKey(data []byte) (session_key.SessionKey, error) {
|
||||
func readBuildRequestRecordReplyKey(data []byte) (common.SessionKey, error) {
|
||||
if len(data) < 168 {
|
||||
return session_key.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
return common.SessionKey{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
session_key := session_key.SessionKey{}
|
||||
session_key := common.SessionKey{}
|
||||
copy(session_key[:], data[136:168])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -368,7 +366,7 @@ func readBuildRequestRecordFlag(data []byte) (int, error) {
|
||||
return 0, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
flag := common.Integer([]byte{data[185]}).Int()
|
||||
flag := int(common.Integer([]byte{data[185]}))
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.readBuildRequestRecordFlag",
|
||||
@ -382,7 +380,7 @@ func readBuildRequestRecordRequestTime(data []byte) (time.Time, error) {
|
||||
return time.Time{}, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
count := common.Integer(data[185:189]).Int()
|
||||
count := int(common.Integer(data[185:189]))
|
||||
rtime := time.Unix(0, 0).Add(time.Duration(count) * time.Hour)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -396,7 +394,7 @@ func readBuildRequestRecordSendMessageID(data []byte) (int, error) {
|
||||
return 0, ERR_BUILD_REQUEST_RECORD_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
send_message_id := common.Integer(data[189:193]).Int()
|
||||
send_message_id := int(common.Integer(data[189:193]))
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.readBuildRequestRecordSendMessageID",
|
||||
|
@ -1,11 +1,10 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
common "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
"github.com/go-i2p/go-i2p/lib/tunnel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadBuildRequestRecordReceiveTunnelTooLittleData(t *testing.T) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
common "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -1,9 +1,7 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
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"
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -145,7 +143,7 @@ type DatabaseLookup struct {
|
||||
ReplyTunnelID [4]byte
|
||||
Size int
|
||||
ExcludedPeers []common.Hash
|
||||
ReplyKey session_key.SessionKey
|
||||
ReplyKey common.SessionKey
|
||||
tags int
|
||||
ReplyTags []session_tag.SessionTag
|
||||
ReplyTags []common.SessionTag
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
common "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -1,7 +1,7 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
common "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -1,9 +1,8 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
"time"
|
||||
|
||||
"github.com/go-i2p/go-i2p/lib/common/certificate"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -67,7 +66,7 @@ type GarlicElGamal []byte
|
||||
type Garlic struct {
|
||||
Count int
|
||||
Cloves []GarlicClove
|
||||
Certificate certificate.Certificate
|
||||
Certificate common.Certificate
|
||||
MessageID int
|
||||
Expiration time.Time
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
"time"
|
||||
|
||||
"github.com/go-i2p/go-i2p/lib/common/certificate"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -46,5 +45,5 @@ type GarlicClove struct {
|
||||
I2NPMessage I2NPMessage
|
||||
CloveID int
|
||||
Expiration time.Time
|
||||
Certificate certificate.Certificate
|
||||
Certificate common.Certificate
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
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"
|
||||
"github.com/go-i2p/go-i2p/lib/tunnel"
|
||||
)
|
||||
|
||||
@ -74,7 +73,7 @@ Total length: Typical length is:
|
||||
|
||||
type GarlicCloveDeliveryInstructions struct {
|
||||
Flag byte
|
||||
SessionKey session_key.SessionKey
|
||||
SessionKey common.SessionKey
|
||||
Hash common.Hash
|
||||
TunnelID tunnel.TunnelID
|
||||
Delay int
|
||||
|
@ -2,11 +2,9 @@ package i2np
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
datalib "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -170,24 +168,24 @@ func ReadI2NPType(data []byte) (int, error) {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
message_type := datalib.Integer([]byte{data[0]})
|
||||
message_type := common.Integer([]byte{data[0]})
|
||||
|
||||
if (message_type.Int() >= 4 || message_type.Int() <= 9) ||
|
||||
(message_type.Int() >= 12 || message_type.Int() <= 17) {
|
||||
if (message_type >= 4 || message_type <= 9) ||
|
||||
(message_type >= 12 || message_type <= 17) {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
}).Warn("unknown_i2np_type")
|
||||
}
|
||||
|
||||
if message_type.Int() >= 224 || message_type.Int() <= 254 {
|
||||
if message_type >= 224 || message_type <= 254 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
}).Warn("experimental_i2np_type")
|
||||
}
|
||||
|
||||
if message_type.Int() == 255 {
|
||||
if message_type == 255 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
@ -198,7 +196,7 @@ func ReadI2NPType(data []byte) (int, error) {
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
}).Debug("parsed_i2np_type")
|
||||
return message_type.Int(), nil
|
||||
return message_type, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPMessageID(data []byte) (int, error) {
|
||||
@ -206,21 +204,21 @@ func ReadI2NPNTCPMessageID(data []byte) (int, error) {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
message_id := datalib.Integer(data[1:5])
|
||||
message_id := common.Integer(data[1:5])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPNTCPMessageID",
|
||||
"type": message_id,
|
||||
}).Debug("parsed_i2np_message_id")
|
||||
return message_id.Int(), nil
|
||||
return message_id, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPMessageExpiration(data []byte) (datalib.Date, error) {
|
||||
func ReadI2NPNTCPMessageExpiration(data []byte) (common.Date, error) {
|
||||
if len(data) < 13 {
|
||||
return datalib.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
return common.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
date := datalib.Date{}
|
||||
date := common.Date{}
|
||||
copy(date[:], data[5:13])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -230,12 +228,12 @@ func ReadI2NPNTCPMessageExpiration(data []byte) (datalib.Date, error) {
|
||||
return date, nil
|
||||
}
|
||||
|
||||
func ReadI2NPSSUMessageExpiration(data []byte) (datalib.Date, error) {
|
||||
func ReadI2NPSSUMessageExpiration(data []byte) (common.Date, error) {
|
||||
if len(data) < 5 {
|
||||
return datalib.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
return common.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
date := datalib.Date{}
|
||||
date := common.Date{}
|
||||
copy(date[4:], data[1:5])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@ -250,13 +248,13 @@ func ReadI2NPNTCPMessageSize(data []byte) (int, error) {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
size := datalib.Integer(data[13:15])
|
||||
size := common.Integer(data[13:15])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPNTCPMessageSize",
|
||||
"size": size,
|
||||
}).Debug("parsed_i2np_message_size")
|
||||
return size.Int(), nil
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPMessageChecksum(data []byte) (int, error) {
|
||||
@ -264,13 +262,13 @@ func ReadI2NPNTCPMessageChecksum(data []byte) (int, error) {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
checksum := datalib.Integer(data[15:16])
|
||||
checksum := common.Integer(data[15:16])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPNTCPMessageCHecksum",
|
||||
"checksum": checksum,
|
||||
}).Debug("parsed_i2np_message_checksum")
|
||||
return checksum.Int(), nil
|
||||
return checksum, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPData(data []byte, size int) ([]byte, error) {
|
||||
|
@ -1,10 +1,9 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
common "github.com/go-i2p/go-i2p/lib/common/data"
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadI2NPTypeWithNoData(t *testing.T) {
|
||||
|
@ -1 +1,5 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
package netdb
|
||||
|
@ -1,15 +1,14 @@
|
||||
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 router_info.RouterInfo
|
||||
ri common.RouterInfo
|
||||
}
|
||||
|
||||
func (e *Entry) WriteTo(w io.Writer) (err error) {
|
||||
|
@ -1,11 +1,9 @@
|
||||
package netdb
|
||||
|
||||
import (
|
||||
"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/common"
|
||||
"github.com/go-i2p/go-i2p/lib/tunnel"
|
||||
"time"
|
||||
)
|
||||
|
||||
// resolves router infos with recursive kademlia lookup
|
||||
@ -18,7 +16,7 @@ type kadResolver struct {
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
func (kr *kadResolver) Lookup(h common.Hash, timeout time.Duration) (chnl chan router_info.RouterInfo) {
|
||||
func (kr *kadResolver) Lookup(h common.Hash, timeout time.Duration) (chnl chan common.RouterInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
package netdb
|
||||
|
||||
import (
|
||||
"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"
|
||||
"github.com/go-i2p/go-i2p/lib/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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 router_info.RouterInfo
|
||||
Lookup(hash common.Hash, timeout time.Duration) chan common.RouterInfo
|
||||
}
|
||||
|
||||
// i2p network database, storage of i2p RouterInfos
|
||||
@ -20,10 +18,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) router_info.RouterInfo
|
||||
GetRouterInfo(hash common.Hash) common.RouterInfo
|
||||
|
||||
// store a router info locally
|
||||
StoreRouterInfo(ri router_info.RouterInfo)
|
||||
StoreRouterInfo(ri common.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
|
||||
|
@ -3,25 +3,23 @@ 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 router_info.RouterInfo) {
|
||||
func (db StdNetDB) GetRouterInfo(hash common.Hash) (chnl chan common.RouterInfo) {
|
||||
fname := db.SkiplistFile(hash)
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
@ -30,11 +28,8 @@ func (db StdNetDB) GetRouterInfo(hash common.Hash) (chnl chan router_info.Router
|
||||
buff := new(bytes.Buffer)
|
||||
_, err = io.Copy(buff, f)
|
||||
f.Close()
|
||||
chnl = make(chan router_info.RouterInfo)
|
||||
ri, _, err := router_info.ReadRouterInfo(buff.Bytes())
|
||||
if err == nil {
|
||||
chnl <- ri
|
||||
}
|
||||
chnl = make(chan common.RouterInfo)
|
||||
chnl <- common.RouterInfo(buff.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
@ -50,7 +45,9 @@ 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
|
||||
@ -116,7 +113,7 @@ func (db StdNetDB) Exists() bool {
|
||||
_, err := os.Stat(p)
|
||||
if err == nil {
|
||||
// check subdirectories for skiplist
|
||||
for _, c := range base64.I2PEncodeAlphabet {
|
||||
for _, c := range base64.Alphabet {
|
||||
if _, err = os.Stat(filepath.Join(p, fmt.Sprintf("r%c", c))); err != nil {
|
||||
return false
|
||||
}
|
||||
@ -128,14 +125,14 @@ func (db StdNetDB) Exists() bool {
|
||||
func (db StdNetDB) SaveEntry(e *Entry) (err error) {
|
||||
var f io.WriteCloser
|
||||
var h common.Hash
|
||||
h = e.ri.IdentHash()
|
||||
//if err == nil {
|
||||
f, err = os.OpenFile(db.SkiplistFile(h), os.O_WRONLY|os.O_CREATE, 0700)
|
||||
h, err = e.ri.IdentHash()
|
||||
if err == nil {
|
||||
err = e.WriteTo(f)
|
||||
f.Close()
|
||||
f, err = os.OpenFile(db.SkiplistFile(h), os.O_WRONLY|os.O_CREATE, 0700)
|
||||
if err == nil {
|
||||
err = e.WriteTo(f)
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
//}
|
||||
if err != nil {
|
||||
log.Errorf("failed to save netdb entry: %s", err.Error())
|
||||
}
|
||||
@ -166,7 +163,7 @@ func (db StdNetDB) Create() (err error) {
|
||||
err = os.Mkdir(p, mode)
|
||||
if err == nil {
|
||||
// create all subdirectories for skiplist
|
||||
for _, c := range base64.I2PEncodeAlphabet {
|
||||
for _, c := range base64.Alphabet {
|
||||
err = os.Mkdir(filepath.Join(p, fmt.Sprintf("r%c", c)), mode)
|
||||
if err != nil {
|
||||
return
|
||||
|
589
lib/su3/su3.go
589
lib/su3/su3.go
@ -1,589 +0,0 @@
|
||||
// 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)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user