58 Commits

Author SHA1 Message Date
7709efff2b Resolve conflict in certificate.go 2024-11-15 11:52:56 -05:00
015c4b23e2 pass sigType to NewRouterInfo 2024-11-15 11:49:10 -05:00
e29c3c7abb formatting + implemented GetSignatureTypeFromCertificate 2024-11-15 11:31:29 -05:00
6f6291a9f6 bounds checking 2024-11-15 11:31:10 -05:00
767864d457 corrected order in Bytes() 2024-11-15 11:30:59 -05:00
0a98236d85 adjusted test 2024-11-15 11:30:40 -05:00
c1fa63f6ec added sigtype 2024-11-15 11:30:34 -05:00
a75c275b4c Corrected Bytes()
-TODO: add logging
2024-11-15 11:30:11 -05:00
d40b3e0cd3 added sig types 2024-11-15 11:14:05 -05:00
2ee2d77d7c check if data slice is empty 2024-11-15 11:01:06 -05:00
dc9b969bcf Add tests for hash 2024-11-11 20:11:58 -05:00
575e877115 gofumpt and fix issues in Certificate, Integer, Mapping, Mapping_Values, String 2024-11-11 18:52:43 -05:00
271cf56ded Fixes the padding generation but the reader still thinks something's wrong 2024-11-10 19:29:07 -05:00
a29fa0bc03 Output the rest of the key certificate, that would probably help... 2024-11-10 17:15:56 -05:00
63c48dd3b7 log the actual bytes in the test for now, make sure they're really there 2024-11-09 13:41:19 -05:00
8bec47efd2 log the actual bytes in the test for now, make sure they're really there 2024-11-09 13:39:38 -05:00
69a50e2035 Try adding a dummy address, change logging to show where not enough data was provided 2024-11-09 13:33:23 -05:00
8319444890 Try adding a dummy address, change logging to show where not enough data was provided 2024-11-09 13:31:24 -05:00
b378661e0e Fix LS test, update Noise Subsystem on README.md 2024-11-09 12:33:53 -05:00
f4086e5f68 !WIP! - Router Info 2024-11-09 11:13:48 -05:00
877fc707c4 !WIP! - Router Info 2024-11-09 01:04:44 -05:00
98d05e27c8 !WIP! - Router Info 2024-11-09 00:53:17 -05:00
4020db8a19 Refactor: Certificate Constructor: 2024-11-04 19:19:03 -05:00
67a02f5d69 Noise: Refactor, remove unused fields 2024-11-04 18:26:17 -05:00
ca1280231c Tidy 2024-11-04 15:25:54 -05:00
02b309df43 Refactor: move HandshakeState to own struct in preparation for NTCP2 mods 2024-11-04 15:20:56 -05:00
a5b3c3f194 NTCP2: Stub out NTCP2 and explain the task at hand, SSU2: Explain the plan 2024-11-04 00:24:08 -05:00
0aa7a5554b Merge branch 'master' of github.com:go-i2p/go-i2p 2024-11-03 23:59:13 -05:00
266a1b71d6 Cleanup: Move obfs.go to own package to avoid import cycle noise->ntcp. I probably should have caught that in review. 2024-11-03 23:58:49 -05:00
idk
d32f2e78ab Merge pull request #21 from hkh4n/config
move base dir from ~/go-i2p to ~/.go-i2p & other changes
2024-11-04 04:51:24 +00:00
idk
9e806bc32e Merge branch 'master' into config 2024-11-03 22:25:05 +00:00
idk
c52112a36f Merge pull request #22 from hkh4n/refactor
go mod tidy & fixed condition that was always true
2024-11-03 22:23:15 +00:00
db0fd9f7e9 Management: rm PASTA.md 2024-11-03 15:53:23 -05:00
3ab258cde6 Management: add ROADMAP.md 2024-11-03 15:53:13 -05:00
20b9bbd8e4 go mod tidy & fixed condition that was always true 2024-11-03 01:52:48 -05:00
1fa520613c replace deprecated function (ioutil -> os) 2024-11-03 00:14:45 -04:00
fb99b98a7e minor typo fix 2024-11-02 21:37:56 -04:00
d6b8cd9d4d go mod tidy 2024-11-02 21:37:29 -04:00
92e4656774 move from ~/go-i2p to ~/.go-i2p 2024-11-02 21:37:23 -04:00
5f2bfb8d9d gofumpt adjustment 2024-11-02 21:36:55 -04:00
idk
9494c226a6 Merge pull request #19 from go-i2p/noise-experimental
Noise Subsystem
2024-11-02 02:59:43 +00:00
idk
344edc6d41 Merge branch 'master' into noise-experimental 2024-11-02 02:59:24 +00:00
idk
9eea99b489 Merge pull request #20 from hkh4n/noise-experimental
Polishes
2024-11-01 14:30:25 +00:00
idk
c984f94b90 Merge pull request #9 from hkh4n/sntp-experimental
Implemented mimic implementation of sntp from the original java implementation
2024-11-01 14:28:00 +00:00
a17f0208dd gofumpt adjustment 2024-10-31 10:53:22 -04:00
487815f8f1 tests 2024-10-31 10:50:21 -04:00
24e0baa879 Moved functions
-encryptPacketDeux -> testEncryptPacket in encrdecr_packet_test.go
-decryptPacketDeux -> testDecryptPacket in encrdecr_packet_test.go
2024-10-31 10:45:42 -04:00
a5d2f0de8c lib/transport/noise/doc.md 2024-10-31 00:01:22 -04:00
423f616d53 Info -> Debug 2024-10-30 23:29:46 -04:00
idk
c65048f3c4 Merge pull request #18 from hkh4n/config
Added config
2024-10-30 22:03:24 +00:00
39b683dac8 fixed circular dependency 2024-10-30 11:20:15 -04:00
46883f6457 build fails -> import cycle 2024-10-30 09:31:01 -04:00
4be55062fc Moved config funcs to config.go 2024-10-30 09:22:32 -04:00
12a3fd4623 Added config files + args
-added routerInstance
-added initConfig()
-added updateRouterConfig()
-yaml format
2024-10-30 09:16:22 -04:00
e468520906 Merge branch 'master' into sntp-experimental 2024-10-20 00:09:42 -04:00
ade80e577c added sntp verification
-Leap Indicator
-Stratum level check
-Round-trip Delay
-Clock offset
-simple non-zero time
-Root Dispersion and Root Delay
2024-10-20 00:07:40 -04:00
f45d301868 proposed refactor
-added secureRandBool() and performTimeQuery()
2024-10-19 22:17:10 -04:00
8fa355f067 !WIP! router_timestamper 2024-09-29 22:03:30 -04:00
89 changed files with 3709 additions and 757 deletions

View File

@ -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.

View File

@ -41,7 +41,7 @@ please keep up with these changes, as they will not be backward compatible and r
- [ ] Elligator2
- [ ] HKDF
- [ ] HMAC
- [/] Noise subsystem
- [X] Noise subsystem
- End-to-End Crypto
- [ ] Garlic messages
- [ ] ElGamal/AES+SessionTag
@ -111,7 +111,7 @@ export DEBUG_I2P=warn
export DEBUG_I2P=error
```
If I2P_DEBUG is set to an unrecognized variable, it will fall back to "debug".
If DEBUG_I2P is set to an unrecognized variable, it will fall back to "debug".
## Contributing

41
ROADMAP.md Normal file
View File

@ -0,0 +1,41 @@
# go-i2p Implementation Roadmap
## Transport Layer (NTCP2)
- Build on existing lib/transport/noise implementation
- Core NTCP2 components:
* Session handshake using noise protocol
* Connection management
* I2NP message transport
## Reseed System
- SU3 file format implementation:
* Format parsing and validation(Much of this work is done in reseed-tools, may need to be moved here)
* Signature verification system(Much of this work is done in reseed-tools, may need to be moved here)
- Local reseed functionality:
* File-based reseed operations
- Self-signed/Package-pinned X.509 certificate handling for reseed validation
## NetDb and Database Store
- Database Store message handling:
* Message structure implementation
* Message handling implementation
- NetDb core implementation:
* RouterInfo management
* LeaseSet management
* Lookup system
* Storage interface
* Peer selection logic?(Maybe do something very basic for now like i2pd used to do, and then improve it later, the important part will be interface design at first)
## Tunnel Implementation
- Tunnel cryptography:
* Key generation and management
* Layered encryption scheme
- Message processing:
* Build request/response handling
* Gateway implementation
* Message forwarding logic
Notes:
- Excluding legacy protocols (SSU1, NTCP1, elgamal, DSA)
- Leveraging existing noise protocol implementation
- SSU2 is not on this roadmap but is fair game for implementation as soon as NTCP2 is done. We're focused on NTCP2 to get this thing sending I2NP messages.

View File

@ -1,15 +1,15 @@
test-crypto-aes-all: test-crypto-aes-core test-crypto-aes-validation test-crypto-aes-padding
test-crypto-aes-core:
go test -v ./lib/crypto -run TestAESEncryptDecrypt
$(GO) test -v ./lib/crypto -run TestAESEncryptDecrypt
test-crypto-aes-validation:
go test -v ./lib/crypto -run TestAESEncryptInvalidKey
go test -v ./lib/crypto -run TestAESDecryptInvalidInput
$(GO) test -v ./lib/crypto -run TestAESEncryptInvalidKey
$(GO) test -v ./lib/crypto -run TestAESDecryptInvalidInput
test-crypto-aes-padding:
go test -v ./lib/crypto -run TestPKCS7PadUnpad
go test -v ./lib/crypto -run TestPKCS7UnpadInvalidInput
$(GO) test -v ./lib/crypto -run TestPKCS7PadUnpad
$(GO) test -v ./lib/crypto -run TestPKCS7UnpadInvalidInput
.PHONY: test-crypto-aes-all \
test-crypto-aes-core \

View File

@ -1,4 +1,4 @@
test-base32-encode-decode-not-mangled:
go test -v ./lib/common/base32 -run TestEncodeDecodeNotMangled
$(GO) test -v ./lib/common/base32 -run TestEncodeDecodeNotMangled
.PHONY: test-base32-encode-decode-not-mangled

View File

@ -1,4 +1,4 @@
test-base64-encode-decode-not-mangled:
go test -v ./lib/common/base64 -run TestEncodeDecodeNotMangled
$(GO) test -v ./lib/common/base64 -run TestEncodeDecodeNotMangled
.PHONY: test-base64-encode-decode-not-mangled

View File

@ -1,22 +1,22 @@
test-build-request-all: test-build-request-receive test-build-request-ident test-build-request-components
test-build-request-receive:
go test -v ./lib/i2np -run TestReadBuildRequestRecordReceiveTunnel
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordReceiveTunnel
test-build-request-ident:
go test -v ./lib/i2np -run TestReadBuildRequestRecordOurIdent
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordOurIdent
test-build-request-components:
go test -v ./lib/i2np -run TestReadBuildRequestRecordNextTunnel
go test -v ./lib/i2np -run TestReadBuildRequestRecordNextIdent
go test -v ./lib/i2np -run TestReadBuildRequestRecordLayerKey
go test -v ./lib/i2np -run TestReadBuildRequestRecordIVKey
go test -v ./lib/i2np -run TestReadBuildRequestRecordReplyKey
go test -v ./lib/i2np -run TestReadBuildRequestRecordReplyIV
go test -v ./lib/i2np -run TestReadBuildRequestRecordFlag
go test -v ./lib/i2np -run TestReadBuildRequestRecordRequestTime
go test -v ./lib/i2np -run TestReadBuildRequestRecordSendMessageID
go test -v ./lib/i2np -run TestReadBuildRequestRecordPadding
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordNextTunnel
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordNextIdent
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordLayerKey
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordIVKey
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordReplyKey
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordReplyIV
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordFlag
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordRequestTime
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordSendMessageID
$(GO) test -v ./lib/i2np -run TestReadBuildRequestRecordPadding
.PHONY: test-build-request-all \
test-build-request-receive \

View File

@ -2,46 +2,46 @@
test-cert-all: test-cert-type test-cert-length test-cert-data test-cert-read test-cert-length-correct test-cert-length-too-short test-cert-length-data-short test-cert-data-correct test-cert-data-too-long test-cert-data-too-short test-cert-read-correct test-cert-read-short test-cert-read-remainder test-cert-read-invalid
test-cert-type:
go test -v ./lib/common/certificate -run TestCertificateTypeIsFirstByte
$(GO) test -v ./lib/common/certificate -run TestCertificateTypeIsFirstByte
test-cert-length:
go test -v ./lib/common/certificate -run TestCertificateLength
$(GO) test -v ./lib/common/certificate -run TestCertificateLength
test-cert-data:
go test -v ./lib/common/certificate -run TestCertificateData
$(GO) test -v ./lib/common/certificate -run TestCertificateData
test-cert-read:
go test -v ./lib/common/certificate -run TestReadCertificate
$(GO) test -v ./lib/common/certificate -run TestReadCertificate
test-cert-length-correct:
go test -v ./lib/common/certificate -run TestCertificateLengthCorrect
$(GO) test -v ./lib/common/certificate -run TestCertificateLengthCorrect
test-cert-length-too-short:
go test -v ./lib/common/certificate -run TestCertificateLengthErrWhenTooShort
$(GO) test -v ./lib/common/certificate -run TestCertificateLengthErrWhenTooShort
test-cert-length-data-short:
go test -v ./lib/common/certificate -run TestCertificateLengthErrWhenDataTooShort
$(GO) test -v ./lib/common/certificate -run TestCertificateLengthErrWhenDataTooShort
test-cert-data-correct:
go test -v ./lib/common/certificate -run TestCertificateDataWhenCorrectSize
$(GO) test -v ./lib/common/certificate -run TestCertificateDataWhenCorrectSize
test-cert-data-too-long:
go test -v ./lib/common/certificate -run TestCertificateDataWhenTooLong
$(GO) test -v ./lib/common/certificate -run TestCertificateDataWhenTooLong
test-cert-data-too-short:
go test -v ./lib/common/certificate -run TestCertificateDataWhenTooShort
$(GO) test -v ./lib/common/certificate -run TestCertificateDataWhenTooShort
test-cert-read-correct:
go test -v ./lib/common/certificate -run TestReadCertificateWithCorrectData
$(GO) test -v ./lib/common/certificate -run TestReadCertificateWithCorrectData
test-cert-read-short:
go test -v ./lib/common/certificate -run TestReadCertificateWithDataTooShort
$(GO) test -v ./lib/common/certificate -run TestReadCertificateWithDataTooShort
test-cert-read-remainder:
go test -v ./lib/common/certificate -run TestReadCertificateWithRemainder
$(GO) test -v ./lib/common/certificate -run TestReadCertificateWithRemainder
test-cert-read-invalid:
go test -v ./lib/common/certificate -run TestReadCertificateWithInvalidLength
$(GO) test -v ./lib/common/certificate -run TestReadCertificateWithInvalidLength
# Declare all targets as PHONY
.PHONY: test-cert-all \

View File

@ -1,2 +1,2 @@
test-date-time-from-milliseconds:
go test -v ./lib/common/data -run TestTimeFromMilliseconds
$(GO) test -v ./lib/common/data -run TestTimeFromMilliseconds

View File

@ -1,17 +1,17 @@
test-crypto-dsa-all: test-crypto-dsa test-crypto-dsa-benchmarks
test-crypto-dsa:
go test -v ./lib/crypto -run TestDSA
$(GO) test -v ./lib/crypto -run TestDSA
test-crypto-dsa-benchmarks:
go test -v ./lib/crypto -bench=DSA -run=^$
$(GO) test -v ./lib/crypto -bench=DSA -run=^$
# Individual benchmarks
test-crypto-dsa-bench-generate:
go test -v ./lib/crypto -bench=DSAGenerate -run=^$
$(GO) test -v ./lib/crypto -bench=DSAGenerate -run=^$
test-crypto-dsa-bench-sign-verify:
go test -v ./lib/crypto -bench=DSASignVerify -run=^$
$(GO) test -v ./lib/crypto -bench=DSASignVerify -run=^$
.PHONY: test-crypto-dsa-all \
test-crypto-dsa \

View File

@ -1,7 +1,7 @@
test-crypto-ed25519-all: test-crypto-ed25519
test-crypto-ed25519:
go test -v ./lib/crypto -run TestEd25519
$(GO) test -v ./lib/crypto -run TestEd25519
.PHONY: test-crypto-ed25519-all \
test-crypto-ed25519

View File

@ -1,20 +1,20 @@
test-crypto-elg-all: test-crypto-elg test-crypto-elg-benchmarks
test-crypto-elg:
go test -v ./lib/crypto -run TestElg
$(GO) test -v ./lib/crypto -run TestElg
test-crypto-elg-benchmarks:
go test -v ./lib/crypto -bench=Elg -run=^$
$(GO) test -v ./lib/crypto -bench=Elg -run=^$
# Individual benchmarks
test-crypto-elg-bench-generate:
go test -v ./lib/crypto -bench=ElgGenerate -run=^$
$(GO) test -v ./lib/crypto -bench=ElgGenerate -run=^$
test-crypto-elg-bench-encrypt:
go test -v ./lib/crypto -bench=ElgEncrypt -run=^$
$(GO) test -v ./lib/crypto -bench=ElgEncrypt -run=^$
test-crypto-elg-bench-decrypt:
go test -v ./lib/crypto -bench=ElgDecrypt -run=^$
$(GO) test -v ./lib/crypto -bench=ElgDecrypt -run=^$
.PHONY: test-crypto-elg-all \
test-crypto-elg \

View File

@ -2,24 +2,24 @@
test-i2np-header-all: test-i2np-type test-i2np-message test-i2np-expiration test-i2np-ntcp-components test-i2np-data test-i2np-regression
test-i2np-type:
go test -v ./lib/i2np -run TestReadI2NPTypeWith
$(GO) test -v ./lib/i2np -run TestReadI2NPTypeWith
test-i2np-message:
go test -v ./lib/i2np -run TestReadI2NPNTCPMessageID
$(GO) test -v ./lib/i2np -run TestReadI2NPNTCPMessageID
test-i2np-expiration:
go test -v ./lib/i2np -run TestReadI2NPNTCPMessageExpiration
go test -v ./lib/i2np -run TestReadI2NPSSUMessageExpiration
$(GO) test -v ./lib/i2np -run TestReadI2NPNTCPMessageExpiration
$(GO) test -v ./lib/i2np -run TestReadI2NPSSUMessageExpiration
test-i2np-ntcp-components:
go test -v ./lib/i2np -run TestReadI2NPNTCPMessageSize
go test -v ./lib/i2np -run TestReadI2NPNTCPMessageChecksum
$(GO) test -v ./lib/i2np -run TestReadI2NPNTCPMessageSize
$(GO) test -v ./lib/i2np -run TestReadI2NPNTCPMessageChecksum
test-i2np-data:
go test -v ./lib/i2np -run TestReadI2NPNTCPData
$(GO) test -v ./lib/i2np -run TestReadI2NPNTCPData
test-i2np-regression:
go test -v ./lib/i2np -run TestCrasherRegression123781
$(GO) test -v ./lib/i2np -run TestCrasherRegression123781
.PHONY: test-i2np-header-all \
test-i2np-type \

View File

@ -1,7 +1,7 @@
test-crypto-hmac-all: test-crypto-hmac
test-crypto-hmac:
go test -v ./lib/crypto -run Test_I2PHMAC
$(GO) test -v ./lib/crypto -run Test_I2PHMAC
.PHONY: test-crypto-hmac-all \
test-crypto-hmac

View File

@ -1,13 +1,13 @@
test-integer-all: test-integer-big-endian test-integer-one-byte test-integer-zero
test-integer-big-endian:
go test -v ./lib/common/integer -run TestIntegerBigEndian
$(GO) test -v ./lib/common/integer -run TestIntegerBigEndian
test-integer-one-byte:
go test -v ./lib/common/integer -run TestWorksWithOneByte
$(GO) test -v ./lib/common/integer -run TestWorksWithOneByte
test-integer-zero:
go test -v ./lib/common/integer -run TestIsZeroWithNoData
$(GO) test -v ./lib/common/integer -run TestIsZeroWithNoData
.PHONY: test-integer-all \
test-integer-big-endian \

View File

@ -1,21 +1,21 @@
test-key-cert-all: test-key-cert-signing test-key-cert-public test-key-cert-construct
test-key-cert-signing:
go test -v ./lib/common/key_certificate -run TestSingingPublicKeyTypeReturnsCorrectInteger
go test -v ./lib/common/key_certificate -run TestSingingPublicKeyTypeReportsWhenDataTooSmall
go test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyReportsWhenDataTooSmall
go test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithDSASHA1
go test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithP256
go test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithP384
go test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithP521
$(GO) test -v ./lib/common/key_certificate -run TestSingingPublicKeyTypeReturnsCorrectInteger
$(GO) test -v ./lib/common/key_certificate -run TestSingingPublicKeyTypeReportsWhenDataTooSmall
$(GO) test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyReportsWhenDataTooSmall
$(GO) test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithDSASHA1
$(GO) test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithP256
$(GO) test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithP384
$(GO) test -v ./lib/common/key_certificate -run TestConstructSigningPublicKeyWithP521
test-key-cert-public:
go test -v ./lib/common/key_certificate -run TestPublicKeyTypeReturnsCorrectInteger
go test -v ./lib/common/key_certificate -run TestPublicKeyTypeReportsWhenDataTooSmall
$(GO) test -v ./lib/common/key_certificate -run TestPublicKeyTypeReturnsCorrectInteger
$(GO) test -v ./lib/common/key_certificate -run TestPublicKeyTypeReportsWhenDataTooSmall
test-key-cert-construct:
go test -v ./lib/common/key_certificate -run TestConstructPublicKeyReportsWhenDataTooSmall
go test -v ./lib/common/key_certificate -run TestConstructPublicKeyReturnsCorrectDataWithElg
$(GO) test -v ./lib/common/key_certificate -run TestConstructPublicKeyReportsWhenDataTooSmall
$(GO) test -v ./lib/common/key_certificate -run TestConstructPublicKeyReturnsCorrectDataWithElg
.PHONY: test-key-cert-all \
test-key-cert-signing \

View File

@ -1,27 +1,27 @@
test-keys-cert-all: test-keys-cert-certificate test-keys-cert-public test-keys-cert-signing test-keys-cert-creation
test-keys-cert-certificate:
go test -v ./lib/common/keys_and_cert -run TestCertificateWithValidData
$(GO) test -v ./lib/common/keys_and_cert -run TestCertificateWithValidData
test-keys-cert-public:
go test -v ./lib/common/keys_and_cert -run TestPublicKeyWithBadData
go test -v ./lib/common/keys_and_cert -run TestPublicKeyWithBadCertificate
go test -v ./lib/common/keys_and_cert -run TestPublicKeyWithNullCertificate
go test -v ./lib/common/keys_and_cert -run TestPublicKeyWithKeyCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestPublicKeyWithBadData
$(GO) test -v ./lib/common/keys_and_cert -run TestPublicKeyWithBadCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestPublicKeyWithNullCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestPublicKeyWithKeyCertificate
test-keys-cert-signing:
go test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithBadData
go test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithBadCertificate
go test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithNullCertificate
go test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithKeyCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithBadData
$(GO) test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithBadCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithNullCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestSigningPublicKeyWithKeyCertificate
test-keys-cert-creation:
go test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithMissingData
go test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithMissingCertData
go test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithCertificate
go test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithoutCertificate
go test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithCertificateAndRemainder
go test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithoutCertificateAndRemainder
$(GO) test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithMissingData
$(GO) test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithMissingCertData
$(GO) test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithoutCertificate
$(GO) test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithCertificateAndRemainder
$(GO) test -v ./lib/common/keys_and_cert -run TestNewKeysAndCertWithValidDataWithoutCertificateAndRemainder
.PHONY: test-keys-cert-all \
test-keys-cert-certificate \

View File

@ -1,20 +1,20 @@
test-lease-set-all: test-lease-set-basic test-lease-set-leases test-lease-set-expiration
test-lease-set-basic:
go test -v ./lib/common/lease_set -run TestDestinationIsCorrect
go test -v ./lib/common/lease_set -run TestPublicKeyIsCorrect
go test -v ./lib/common/lease_set -run TestSigningKeyIsCorrect
go test -v ./lib/common/lease_set -run TestSignatureIsCorrect
$(GO) test -v ./lib/common/lease_set -run TestDestinationIsCorrect
$(GO) test -v ./lib/common/lease_set -run TestPublicKeyIsCorrect
$(GO) test -v ./lib/common/lease_set -run TestSigningKeyIsCorrect
$(GO) test -v ./lib/common/lease_set -run TestSignatureIsCorrect
test-lease-set-leases:
go test -v ./lib/common/lease_set -run TestLeaseCountCorrect
go test -v ./lib/common/lease_set -run TestLeaseCountCorrectWithMultiple
go test -v ./lib/common/lease_set -run TestLeaseCountErrorWithTooMany
go test -v ./lib/common/lease_set -run TestLeasesHaveCorrectData
$(GO) test -v ./lib/common/lease_set -run TestLeaseCountCorrect
$(GO) test -v ./lib/common/lease_set -run TestLeaseCountCorrectWithMultiple
$(GO) test -v ./lib/common/lease_set -run TestLeaseCountErrorWithTooMany
$(GO) test -v ./lib/common/lease_set -run TestLeasesHaveCorrectData
test-lease-set-expiration:
go test -v ./lib/common/lease_set -run TestNewestExpirationIsCorrect
go test -v ./lib/common/lease_set -run TestOldestExpirationIsCorrect
$(GO) test -v ./lib/common/lease_set -run TestNewestExpirationIsCorrect
$(GO) test -v ./lib/common/lease_set -run TestOldestExpirationIsCorrect
.PHONY: test-lease-set-all \
test-lease-set-basic \

View File

@ -1,25 +1,25 @@
test-mapping-all: test-mapping-values test-mapping-duplicates test-mapping-conversion test-mapping-utils
test-mapping-values:
go test -v ./lib/common/data -run TestValuesExclusesPairWithBadData
go test -v ./lib/common/data -run TestValuesWarnsMissingData
go test -v ./lib/common/data -run TestValuesWarnsExtraData
go test -v ./lib/common/data -run TestValuesEnforcesEqualDelimitor
go test -v ./lib/common/data -run TestValuesEnforcedSemicolonDelimitor
go test -v ./lib/common/data -run TestValuesReturnsValues
$(GO) test -v ./lib/common/data -run TestValuesExclusesPairWithBadData
$(GO) test -v ./lib/common/data -run TestValuesWarnsMissingData
$(GO) test -v ./lib/common/data -run TestValuesWarnsExtraData
$(GO) test -v ./lib/common/data -run TestValuesEnforcesEqualDelimitor
$(GO) test -v ./lib/common/data -run TestValuesEnforcedSemicolonDelimitor
$(GO) test -v ./lib/common/data -run TestValuesReturnsValues
test-mapping-duplicates:
go test -v ./lib/common/data -run TestHasDuplicateKeysTrueWhenDuplicates
go test -v ./lib/common/data -run TestHasDuplicateKeysFalseWithoutDuplicates
go test -v ./lib/common/data -run TestReadMappingHasDuplicateKeys
$(GO) test -v ./lib/common/data -run TestHasDuplicateKeysTrueWhenDuplicates
$(GO) test -v ./lib/common/data -run TestHasDuplicateKeysFalseWithoutDuplicates
$(GO) test -v ./lib/common/data -run TestReadMappingHasDuplicateKeys
test-mapping-conversion:
go test -v ./lib/common/data -run TestGoMapToMappingProducesCorrectMapping
go test -v ./lib/common/data -run TestFullGoMapToMappingProducesCorrectMapping
$(GO) test -v ./lib/common/data -run TestGoMapToMappingProducesCorrectMapping
$(GO) test -v ./lib/common/data -run TestFullGoMapToMappingProducesCorrectMapping
test-mapping-utils:
go test -v ./lib/common/data -run TestStopValueRead
go test -v ./lib/common/data -run TestBeginsWith
$(GO) test -v ./lib/common/data -run TestStopValueRead
$(GO) test -v ./lib/common/data -run TestBeginsWith
.PHONY: test-mapping-all \
test-mapping-values \

View File

@ -1,2 +1,2 @@
test-mapping-values-order:
go test -v ./lib/common/data -run TestMappingOrderSortsValuesThenKeys
$(GO) test -v ./lib/common/data -run TestMappingOrderSortsValuesThenKeys

View File

@ -1,11 +1,19 @@
test-noise-transport-all: test-noise-packet-encryption test-noise-transport-connection
test-noise-transport-all: test-noise-packet-encryption test-noise-transport-connection test-noise-packet-obfuscation test-noise-packet-obfuscation-func
test-noise-packet-encryption:
go test -v ./lib/transport/noise -run TestEncryptDecryptPacketOffline
$(GO) test -v ./lib/transport/noise -run TestEncryptDecryptPacketOffline
test-noise-transport-connection:
go test -v ./lib/transport/noise -run TestTransport
$(GO) test -v ./lib/transport/noise -run TestTransport
test-noise-packet-obfuscation:
$(GO) test -v ./lib/transport/noise -run TestEncryptDecryptPacketObfsOffline
test-noise-packet-obfuscation-func:
$(GO) test -v ./lib/transport/noise -run TestEncryptDecryptPacketObfsOfflineWithFunc
.PHONY: test-noise-transport-all \
test-noise-packet-encryption \
test-noise-transport-connection
test-noise-transport-connection \
test-noise-packet-obfuscation \
test-noise-packet-obfuscation-func

View File

@ -1,17 +1,17 @@
test-router-address-all: test-router-address-validation test-router-address-functionality test-router-address-fuzz
test-router-address-validation:
go test -v ./lib/common/router_address -run TestCheckValidReportsEmptySlice
go test -v ./lib/common/router_address -run TestCheckRouterAddressValidReportsDataMissing
go test -v ./lib/common/router_address -run TestCheckRouterAddressValidNoErrWithValidData
$(GO) test -v ./lib/common/router_address -run TestCheckValidReportsEmptySlice
$(GO) test -v ./lib/common/router_address -run TestCheckRouterAddressValidReportsDataMissing
$(GO) test -v ./lib/common/router_address -run TestCheckRouterAddressValidNoErrWithValidData
test-router-address-functionality:
go test -v ./lib/common/router_address -run TestRouterAddressCostReturnsFirstByte
go test -v ./lib/common/router_address -run TestRouterAddressExpirationReturnsCorrectData
go test -v ./lib/common/router_address -run TestReadRouterAddressReturnsCorrectRemainderWithoutError
$(GO) test -v ./lib/common/router_address -run TestRouterAddressCostReturnsFirstByte
$(GO) test -v ./lib/common/router_address -run TestRouterAddressExpirationReturnsCorrectData
$(GO) test -v ./lib/common/router_address -run TestReadRouterAddressReturnsCorrectRemainderWithoutError
test-router-address-fuzz:
go test -v ./lib/common/router_address -run TestCorrectsFuzzCrasher1
$(GO) test -v ./lib/common/router_address -run TestCorrectsFuzzCrasher1
.PHONY: test-router-address-all \
test-router-address-validation \

View File

@ -1,23 +1,23 @@
test-router-info-all: test-router-info-published test-router-info-addresses test-router-info-identity test-router-info-misc
test-router-info-published:
go test -v ./lib/common/router_info -run TestPublishedReturnsCorrectDate
go test -v ./lib/common/router_info -run TestPublishedReturnsCorrectErrorWithPartialDate
go test -v ./lib/common/router_info -run TestPublishedReturnsCorrectErrorWithInvalidData
$(GO) test -v ./lib/common/router_info -run TestPublishedReturnsCorrectDate
$(GO) test -v ./lib/common/router_info -run TestPublishedReturnsCorrectErrorWithPartialDate
$(GO) test -v ./lib/common/router_info -run TestPublishedReturnsCorrectErrorWithInvalidData
test-router-info-addresses:
go test -v ./lib/common/router_info -run TestRouterAddressCountReturnsCorrectCount
go test -v ./lib/common/router_info -run TestRouterAddressCountReturnsCorrectErrorWithInvalidData
go test -v ./lib/common/router_info -run TestRouterAddressesReturnsAddresses
go test -v ./lib/common/router_info -run TestRouterAddressesReturnsAddressesWithMultiple
$(GO) test -v ./lib/common/router_info -run TestRouterAddressCountReturnsCorrectCount
$(GO) test -v ./lib/common/router_info -run TestRouterAddressCountReturnsCorrectErrorWithInvalidData
$(GO) test -v ./lib/common/router_info -run TestRouterAddressesReturnsAddresses
$(GO) test -v ./lib/common/router_info -run TestRouterAddressesReturnsAddressesWithMultiple
test-router-info-identity:
go test -v ./lib/common/router_info -run TestRouterIdentityIsCorrect
$(GO) test -v ./lib/common/router_info -run TestRouterIdentityIsCorrect
test-router-info-misc:
go test -v ./lib/common/router_info -run TestPeerSizeIsZero
go test -v ./lib/common/router_info -run TestOptionsAreCorrect
go test -v ./lib/common/router_info -run TestSignatureIsCorrectSize
$(GO) test -v ./lib/common/router_info -run TestPeerSizeIsZero
$(GO) test -v ./lib/common/router_info -run TestOptionsAreCorrect
$(GO) test -v ./lib/common/router_info -run TestSignatureIsCorrectSize
.PHONY: test-router-info-all \
test-router-info-published \

View File

@ -1,24 +1,24 @@
test-string-all: test-string-length test-string-data test-string-conversion test-string-read
test-string-length:
go test -v ./lib/common/data -run TestStringReportsCorrectLength
go test -v ./lib/common/data -run TestI2PStringReportsLengthZeroError
go test -v ./lib/common/data -run TestI2PStringReportsExtraDataError
go test -v ./lib/common/data -run TestI2PStringDataReportsLengthZeroError
$(GO) test -v ./lib/common/data -run TestStringReportsCorrectLength
$(GO) test -v ./lib/common/data -run TestI2PStringReportsLengthZeroError
$(GO) test -v ./lib/common/data -run TestI2PStringReportsExtraDataError
$(GO) test -v ./lib/common/data -run TestI2PStringDataReportsLengthZeroError
test-string-data:
go test -v ./lib/common/data -run TestI2PStringDataReportsExtraDataError
go test -v ./lib/common/data -run TestI2PStringDataEmptyWhenZeroLength
go test -v ./lib/common/data -run TestI2PStringDataErrorWhenNonZeroLengthOnly
$(GO) test -v ./lib/common/data -run TestI2PStringDataReportsExtraDataError
$(GO) test -v ./lib/common/data -run TestI2PStringDataEmptyWhenZeroLength
$(GO) test -v ./lib/common/data -run TestI2PStringDataErrorWhenNonZeroLengthOnly
test-string-conversion:
go test -v ./lib/common/data -run TestToI2PI2PStringFormatsCorrectly
go test -v ./lib/common/data -run TestToI2PStringReportsOverflows
$(GO) test -v ./lib/common/data -run TestToI2PI2PStringFormatsCorrectly
$(GO) test -v ./lib/common/data -run TestToI2PStringReportsOverflows
test-string-read:
go test -v ./lib/common/data -run TestReadStringReadsLength
go test -v ./lib/common/data -run TestReadI2PStringErrWhenEmptySlice
go test -v ./lib/common/data -run TestReadI2PStringErrWhenDataTooShort
$(GO) test -v ./lib/common/data -run TestReadStringReadsLength
$(GO) test -v ./lib/common/data -run TestReadI2PStringErrWhenEmptySlice
$(GO) test -v ./lib/common/data -run TestReadI2PStringErrWhenDataTooShort
.PHONY: test-string-all \
test-string-length \

View File

@ -1,10 +1,10 @@
test-su3-all: test-su3-read test-su3-signature
test-su3-read:
go test -v ./lib/su3 -run TestRead
$(GO) test -v ./lib/su3 -run TestRead
test-su3-signature:
go test -v ./lib/su3 -run TestReadSignatureFirst
$(GO) test -v ./lib/su3 -run TestReadSignatureFirst
.PHONY: test-su3-all \
test-su3-read \

View File

@ -2,18 +2,18 @@ test-tunnel-all: test-tunnel-delivery-instructions test-tunnel-message
# Tests from delivery_test.go
test-tunnel-delivery-instructions:
go test -v ./lib/tunnel -run TestReadDeliveryInstructions
$(GO) test -v ./lib/tunnel -run TestReadDeliveryInstructions
# Tests from message_test.go
test-tunnel-message: test-tunnel-message-padding test-tunnel-message-fragments
test-tunnel-message-padding:
go test -v ./lib/tunnel -run TestDeliveryInstructionDataWithNoPadding
go test -v ./lib/tunnel -run TestDeliveryInstructionDataWithSomePadding
go test -v ./lib/tunnel -run TestDeliveryInstructionDataWithOnlyPadding
$(GO) test -v ./lib/tunnel -run TestDeliveryInstructionDataWithNoPadding
$(GO) test -v ./lib/tunnel -run TestDeliveryInstructionDataWithSomePadding
$(GO) test -v ./lib/tunnel -run TestDeliveryInstructionDataWithOnlyPadding
test-tunnel-message-fragments:
go test -v ./lib/tunnel -run TestDeliveryInstructionsWithFragments
$(GO) test -v ./lib/tunnel -run TestDeliveryInstructionsWithFragments
.PHONY: test-tunnel-all \
test-tunnel-delivery-instructions \

28
go.mod
View File

@ -3,19 +3,41 @@ module github.com/go-i2p/go-i2p
go 1.23.1
require (
github.com/beevik/ntp v1.4.3
github.com/emirpasic/gods v1.18.1
github.com/eyedeekay/go-unzip v0.0.0-20240201194209-560d8225b50e
github.com/flynn/noise v1.1.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
go.step.sm/crypto v0.53.0
golang.org/x/crypto v0.27.0
gopkg.in/yaml.v3 v3.0.1
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
golang.org/x/text v0.18.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

71
go.sum
View File

@ -1,43 +1,106 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/eyedeekay/go-unzip v0.0.0-20240201194209-560d8225b50e h1:NMjWYVkgcQHGOy0/VxU0TU6smrcoxzj9hwDesx2sB0w=
github.com/eyedeekay/go-unzip v0.0.0-20240201194209-560d8225b50e/go.mod h1:fKfFM3BsOOyjtZmEty7FsGzGabXo8Eb/dHjyIhTtxsE=
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.step.sm/crypto v0.53.0 h1:+1as1ogzuCzx15/468M4mEC5juogI5a0Fzbsyh1CuYY=
go.step.sm/crypto v0.53.0/go.mod h1:AqLU78RqNUHepLzyOWZuNN/2++Lu7dZENdO9UzWOGSk=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -3,6 +3,8 @@
package certificate
import (
"encoding/binary"
"errors"
"fmt"
"github.com/sirupsen/logrus"
@ -94,14 +96,39 @@ func (c *Certificate) ExcessBytes() []byte {
return nil
}
// Bytes returns the entire certificate in []byte form, trims payload to specified length.
// Data returns the payload of a Certificate, payload is trimmed to the specified length
func (c *Certificate) Data() []byte {
length := c.Length()
if length == 0 {
return []byte{}
}
if length > len(c.payload) {
log.WithFields(logrus.Fields{
"specified_length": length,
"actual_length": len(c.payload),
}).Warn("Certificate payload shorter than specified length")
return c.payload
}
return c.payload[:length]
}
// Bytes returns the entire certificate in []byte form
func (c *Certificate) Bytes() []byte {
if c.kind.Int() == CERT_NULL {
return []byte{0, 0, 0}
}
bytes := c.kind.Bytes()
bytes = append(bytes, c.len.Bytes()...)
bytes = append(bytes, c.Data()...)
log.WithFields(logrus.Fields{
"bytes_length": len(bytes),
"cert_type": c.kind.Int(),
}).Debug("Generated bytes for certificate")
return bytes
}
@ -128,24 +155,9 @@ func (c *Certificate) Length() (length 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
log.Warn("Certificate payload shorter than specified length")
} else {
data = c.payload[0:lastElement]
}
log.WithFields(logrus.Fields{
"data_length": len(data),
}).Debug("Retrieved certificate data")
return
}
// NewCertificate creates a new Certficiate from []byte
// readCertificate 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) {
func readCertificate(data []byte) (certificate Certificate, err error) {
certificate = Certificate{}
switch len(data) {
case 0:
@ -154,50 +166,73 @@ func NewCertificate(data []byte) (certificate Certificate, err error) {
log.WithFields(logrus.Fields{
"at": "(Certificate) NewCertificate",
"certificate_bytes_length": len(data),
"reason": "too short (len < CERT_MIN_SIZE)" + fmt.Sprintf("%d", certificate.kind.Int()),
"reason": "too short (len < CERT_MIN_SIZE)",
}).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.kind = Integer(data[0:1])
certificate.len = Integer([]byte{0})
log.WithFields(logrus.Fields{
"at": "(Certificate) NewCertificate",
"certificate_bytes_length": len(data),
"reason": "too short (len < CERT_MIN_SIZE)" + fmt.Sprintf("%d", certificate.kind.Int()),
"reason": "too short (len < CERT_MIN_SIZE)",
}).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")
// Validate certificate type
if certificate.kind.Int() < CERT_NULL || certificate.kind.Int() > CERT_KEY {
log.WithFields(logrus.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")
"at": "(Certificate) NewCertificate",
"type": certificate.kind.Int(),
}).Error("invalid certificate type")
err = fmt.Errorf("error parsing certificate: invalid type: %d", certificate.kind.Int())
return
}
log.WithFields(logrus.Fields{
"type": certificate.kind.Int(),
"length": certificate.len.Int(),
}).Debug("Successfully created new certificate")
return
// Handle NULL certificates
if certificate.kind.Int() == CERT_NULL && certificate.len.Int() != 0 {
log.WithFields(logrus.Fields{
"at": "(Certificate) NewCertificate",
"length": certificate.len.Int(),
}).Error("NULL certificate must have zero length")
err = fmt.Errorf("error parsing certificate: NULL certificate must have zero length")
return
}
// Validate payload length
expectedLength := certificate.len.Int()
actualLength := len(data) - CERT_MIN_SIZE
if expectedLength > actualLength {
log.WithFields(logrus.Fields{
"at": "(Certificate) NewCertificate",
"expected_length": expectedLength,
"actual_length": actualLength,
}).Error("certificate data shorter than specified length")
err = fmt.Errorf("error parsing certificate: data shorter than specified length")
return
}
certificate.payload = data[CERT_MIN_SIZE:]
}
log.WithFields(logrus.Fields{
"type": certificate.kind.Int(),
"length": certificate.len.Int(),
}).Debug("Successfully parsed certificate")
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)
certificate, err = readCertificate(data)
if err != nil && err.Error() == "certificate parsing warning: certificate data is longer than specified by length" {
log.Warn("Certificate data longer than specified length")
err = nil
@ -208,3 +243,53 @@ func ReadCertificate(data []byte) (certificate Certificate, remainder []byte, er
}).Debug("Read certificate and extracted remainder")
return
}
// NewCertificate creates a new Certificate with default NULL type
func NewCertificate() *Certificate {
return &Certificate{
kind: Integer([]byte{CERT_NULL}),
len: Integer([]byte{0}),
payload: make([]byte, 0),
}
}
// NewCertificateWithType creates a new Certificate with specified type and payload
func NewCertificateWithType(certType uint8, payload []byte) (*Certificate, error) {
// Validate certificate type
switch certType {
case CERT_NULL, CERT_HASHCASH, CERT_HIDDEN, CERT_SIGNED, CERT_MULTIPLE, CERT_KEY:
// Valid type
default:
return nil, fmt.Errorf("invalid certificate type: %d", certType)
}
// For NULL certificates, payload should be empty
if certType == CERT_NULL && len(payload) > 0 {
return nil, errors.New("NULL certificates must have empty payload")
}
length, _ := NewIntegerFromInt(len(payload), 2)
cert := &Certificate{
kind: Integer([]byte{certType}),
len: *length,
payload: make([]byte, len(payload)),
}
// Copy payload if present
if len(payload) > 0 {
copy(cert.payload, payload)
}
return cert, nil
}
func GetSignatureTypeFromCertificate(cert Certificate) (int, error) {
if cert.Type() != CERT_KEY {
return 0, fmt.Errorf("unexpected certificate type: %d", cert.Type)
}
if len(cert.payload) < 2 {
return 0, fmt.Errorf("certificate payload too short to contain signature type")
}
sigType := int(binary.BigEndian.Uint16(cert.payload[0:2]))
return sigType, nil
}

View File

@ -10,7 +10,7 @@ func TestCertificateTypeIsFirstByte(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x00}
certificate, err := NewCertificate(bytes)
certificate, err := readCertificate(bytes)
cert_type := certificate.Type()
assert.Equal(cert_type, 3, "certificate.Type() should be the first bytes in a certificate")
@ -21,7 +21,7 @@ func TestCertificateLengthCorrect(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x02, 0xff, 0xff}
certificate, err := NewCertificate(bytes)
certificate, err := readCertificate(bytes)
cert_len := certificate.Length()
assert.Equal(cert_len, 2, "certificate.Length() should return integer from second two bytes")
@ -45,7 +45,7 @@ func TestCertificateLengthErrWhenDataTooShort(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x02, 0xff}
certificate, err := NewCertificate(bytes)
certificate, err := readCertificate(bytes)
cert_len := certificate.Length()
assert.Equal(cert_len, 2, "certificate.Length() did not return indicated length when data was actually missing")
@ -58,7 +58,7 @@ func TestCertificateDataWhenCorrectSize(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x01, 0xaa}
certificate, err := NewCertificate(bytes)
certificate, err := readCertificate(bytes)
cert_data := certificate.Data()
assert.Nil(err, "certificate.Data() returned error with valid data")
@ -85,7 +85,7 @@ func TestCertificateDataWhenTooShort(t *testing.T) {
assert := assert.New(t)
bytes := []byte{0x03, 0x00, 0x02, 0xff}
certificate, err := NewCertificate(bytes)
certificate, err := readCertificate(bytes)
cert_data := certificate.Data()
if assert.NotNil(err) {

View File

@ -38,7 +38,8 @@ func (i Date) Bytes() []byte {
// Int returns the Date as a Go integer.
func (i Date) Int() int {
return intFromBytes(i.Bytes())
val, _ := intFromBytes(i.Bytes())
return val
}
// Time takes the value stored in date as an 8 byte big-endian integer representing the

View File

@ -2,6 +2,8 @@ package data
import (
"crypto/sha256"
"crypto/subtle"
"errors"
"io"
)
@ -10,38 +12,68 @@ import (
Accurate for version 0.9.49
Description
Represents the SHA256 of some data.
Represents the SHA256 of some data. Used throughout I2P for data verification
and identity representation. Must be compared using constant-time operations
to prevent timing attacks.
Contents
32 bytes
[I2P Hash]:
32 bytes representing a SHA256 hash value
*/
// Hash is the represenation of an I2P Hash.
//
var (
ErrInvalidHashSize = errors.New("invalid hash size")
ErrNilReader = errors.New("nil reader")
)
// Hash is the representation of an I2P Hash.
// It is always exactly 32 bytes containing a SHA256 sum.
//
// https://geti2p.net/spec/common-structures#hash
type Hash [32]byte
// Bytes returns a copy of the Hash as a 32-byte array.
// This prevents modification of the original hash value.
func (h Hash) Bytes() [32]byte {
return h
}
// Equal compares two hashes in constant time.
// Returns true if the hashes are identical.
func (h Hash) Equal(other Hash) bool {
return subtle.ConstantTimeCompare(h[:], other[:]) == 1
}
// IsZero returns true if the hash is all zeros.
func (h Hash) IsZero() bool {
var zero Hash
return h.Equal(zero)
}
// HashData returns the SHA256 sum of a []byte input as Hash.
func HashData(data []byte) (h Hash) {
// log.Println("Hashing Data:", data)
h = sha256.Sum256(data)
return
// Never returns an error as SHA256 operates on any input length.
func HashData(data []byte) Hash {
if data == nil {
data = []byte{} // Handle nil input gracefully
}
return sha256.Sum256(data)
}
// 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)
// Returns an error if one occurs while reading from reader or if reader is nil.
func HashReader(r io.Reader) (Hash, error) {
var h Hash
if r == nil {
return h, ErrNilReader
}
return
}
sha := sha256.New()
_, err := io.Copy(sha, r)
if err != nil {
return h, err
}
sum := sha.Sum(nil)
copy(h[:], sum)
return h, nil
}

View File

@ -0,0 +1,65 @@
package data
import (
"io"
"strings"
"testing"
)
func TestHash(t *testing.T) {
tests := []struct {
name string
data []byte
want Hash
}{
{
name: "Empty input",
data: []byte{},
want: HashData([]byte{}),
},
{
name: "Nil input",
data: nil,
want: HashData([]byte{}),
},
// Add more test cases
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := HashData(tt.data)
if !got.Equal(tt.want) {
t.Errorf("HashData() = %v, want %v", got, tt.want)
}
})
}
}
func TestHashReader(t *testing.T) {
tests := []struct {
name string
reader io.Reader
wantErr bool
}{
{
name: "Nil reader",
reader: nil,
wantErr: true,
},
{
name: "Empty reader",
reader: strings.NewReader(""),
wantErr: false,
},
// Add more test cases
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := HashReader(tt.reader)
if (err != nil) != tt.wantErr {
t.Errorf("HashReader() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -2,13 +2,15 @@ package data
import (
"encoding/binary"
"errors"
"math"
)
// MAX_INTEGER_SIZE is the maximum length of an I2P integer in bytes.
const MAX_INTEGER_SIZE = 8
/*
[I2P Hash]
[I2P Integer]
Accurate for version 0.9.49
Description
@ -18,68 +20,112 @@ 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
var (
// ErrInvalidSize indicates the requested integer size is invalid (<=0 or >MAX_INTEGER_SIZE)
ErrInvalidSize = errors.New("invalid integer size")
// ErrInsufficientData indicates there isn't enough data to read the requested size
ErrInsufficientData = errors.New("insufficient data")
// ErrNegativeValue indicates an attempt to create an Integer from a negative value
ErrNegativeValue = errors.New("negative values not allowed")
// ErrIntegerOverflow indicates the value exceeds the maximum allowed size
ErrIntegerOverflow = errors.New("integer overflow")
)
// Integer is the representation of an I2P Integer.
// It contains 1 to 8 bytes in network byte order (big endian)
// representing an unsigned integer value.
type Integer []byte
// Bytes returns the raw []byte content of an Integer.
// This represents the big-endian encoded form of the integer.
func (i Integer) Bytes() []byte {
return i[:]
return i
}
// Int returns the Date as a Go integer
// Int returns the Integer as a Go integer.
// Returns an error if the value would overflow on the current platform
// or if the encoding is invalid.
func (i Integer) Int() int {
return intFromBytes(i.Bytes())
val, _ := intFromBytes(i)
return val
}
// 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):]
// Returns an error if size is invalid or there isn't enough data.
func ReadInteger(bytes []byte, size int) (Integer, []byte, error) {
if size <= 0 {
return nil, bytes, ErrInvalidSize
}
return bytes[:size], bytes[size:]
if size > len(bytes) {
return nil, bytes, ErrInsufficientData
}
return Integer(bytes[:size]), bytes[size:], nil
}
// 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
// Returns a pointer to Integer and the remaining bytes.
// Returns an error if size is invalid or there isn't enough data.
func NewInteger(bytes []byte, size int) (*Integer, []byte, error) {
if size <= 0 || size > MAX_INTEGER_SIZE {
return nil, bytes, ErrInvalidSize
}
intBytes := bytes[:integerSize]
remainder = bytes[integerSize:]
i, _ := ReadInteger(intBytes, integerSize)
integer = &i
return
if len(bytes) < size {
return nil, bytes, ErrInsufficientData
}
integer, remainder, err := ReadInteger(bytes, size)
if err != nil {
return nil, bytes, err
}
return &integer, remainder, nil
}
// 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
// The value must be non-negative and fit within the specified number of bytes.
// Returns an error if the size is invalid or the value cannot be represented.
func NewIntegerFromInt(value int, size int) (*Integer, error) {
if size <= 0 || size > MAX_INTEGER_SIZE {
return nil, ErrInvalidSize
}
objinteger, _, err := NewInteger(bytes[MAX_INTEGER_SIZE-integerSize:], integerSize)
integer = objinteger
return
if value < 0 {
return nil, ErrNegativeValue
}
// Check if value fits in specified size
maxVal := int(math.Pow(2, float64(size*8))) - 1
if value > maxVal {
return nil, ErrIntegerOverflow
}
buf := make([]byte, MAX_INTEGER_SIZE)
binary.BigEndian.PutUint64(buf, uint64(value))
data := buf[MAX_INTEGER_SIZE-size:]
integer := Integer(data)
return &integer, nil
}
// 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...,
)
// intFromBytes interprets a slice of bytes from length 0 to length 8 as a big-endian
// integer and returns an int representation.
// Returns an error if the value would overflow on the current platform
// or if the input is invalid.
func intFromBytes(number []byte) (int, error) {
if len(number) == 0 {
return 0, nil
}
value = int(binary.BigEndian.Uint64(number))
return
}
if len(number) > MAX_INTEGER_SIZE {
return 0, ErrInvalidSize
}
padded := make([]byte, MAX_INTEGER_SIZE)
copy(padded[MAX_INTEGER_SIZE-len(number):], number)
val := int64(binary.BigEndian.Uint64(padded))
if val > math.MaxInt32 || val < math.MinInt32 {
return 0, ErrIntegerOverflow
}
return int(val), nil
}

View File

@ -30,3 +30,32 @@ func TestIsZeroWithNoData(t *testing.T) {
assert.Equal(integer.Int(), 0, "Integer() did not correctly parse zero length byte slice")
}
func TestIntegerEdgeCases(t *testing.T) {
tests := []struct {
name string
input []byte
size int
wantErr bool
wantInt int
}{
{"empty input", []byte{}, 1, true, 0},
{"zero size", []byte{1}, 0, true, 0},
{"oversized", []byte{1}, 9, true, 0},
{"valid small", []byte{42}, 1, false, 42},
{"valid max", []byte{1, 2, 3, 4, 5, 6, 7, 8}, 8, false, 72623859790382856},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i, _, err := NewInteger(tt.input, tt.size)
if (err != nil) != tt.wantErr {
t.Errorf("NewInteger() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil && i.Int() != tt.wantInt {
t.Errorf("Integer.Int() = %v, want %v", i.Int(), tt.wantInt)
}
})
}
}

View File

@ -2,6 +2,7 @@ package data
import (
"errors"
"fmt"
"github.com/sirupsen/logrus"
)
@ -97,34 +98,20 @@ func (mapping *Mapping) HasDuplicateKeys() bool {
}
// GoMapToMapping converts a Go map of unformatted strings to *Mapping.
func GoMapToMapping(gomap map[string]string) (mapping *Mapping, err error) {
log.WithFields(logrus.Fields{
"input_map_size": len(gomap),
}).Debug("Converting Go map to Mapping")
func GoMapToMapping(gomap map[string]string) (*Mapping, error) {
map_vals := MappingValues{}
for k, v := range gomap {
key_str, kerr := ToI2PString(k)
if kerr != nil {
log.WithError(kerr).Error("Failed to convert key to I2PString")
err = kerr
return
key_str, err := ToI2PString(k)
if err != nil {
return nil, fmt.Errorf("key conversion error: %w", err)
}
val_str, verr := ToI2PString(v)
if verr != nil {
log.WithError(verr).Error("Failed to convert value to I2PString")
err = verr
return
val_str, err := ToI2PString(v)
if err != nil {
return nil, fmt.Errorf("value conversion error: %w", err)
}
map_vals = append(
map_vals,
[2]I2PString{key_str, val_str},
)
map_vals = append(map_vals, [2]I2PString{key_str, val_str})
}
mapping = ValuesToMapping(map_vals)
log.WithFields(logrus.Fields{
"mapping_size": len(map_vals),
}).Debug("Successfully converted Go map to Mapping")
return
return ValuesToMapping(map_vals), nil
}
// Check if the string parsing error indicates that the Mapping
@ -152,10 +139,37 @@ func beginsWith(bytes []byte, chr byte) bool {
return result
}
func (mapping *Mapping) addValue(key, value I2PString) error {
for _, pair := range *mapping.vals {
existingKey, _ := pair[0].Data()
newKey, _ := key.Data()
if existingKey == newKey {
return fmt.Errorf("duplicate key: %s", newKey)
}
}
*mapping.vals = append(*mapping.vals, [2]I2PString{key, value})
return nil
}
// 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.
const MaxMappingSize = 65535 // Match Java I2P's maximum mapping size
func ReadMapping(bytes []byte) (mapping Mapping, remainder []byte, err []error) {
if len(bytes) < 3 {
err = append(err, errors.New("mapping data too short"))
return
}
size, remainder, e := NewInteger(bytes, 2)
if e != nil {
log.WithError(e).Error("Failed to read Mapping size")
err = append(err, e)
}
if size.Int() > MaxMappingSize {
err = append(err, fmt.Errorf("mapping size %d exceeds maximum %d", size.Int(), MaxMappingSize))
return
}
log.WithFields(logrus.Fields{
"input_length": len(bytes),
}).Debug("Reading Mapping from bytes")
@ -168,16 +182,16 @@ func ReadMapping(bytes []byte) (mapping Mapping, remainder []byte, err []error)
err = append(err, e)
return
}
size, remainder, e := NewInteger(bytes, 2)
if e != nil {
log.WithError(e).Error("Failed to read Mapping size")
err = append(err, e)
}
if size.Int() == 0 {
log.Warn("Mapping size is zero")
return
}
mapping.size = size
if mapping.size.Int() > len(remainder) {
err = append(err, fmt.Errorf("mapping size %d exceeds available data length %d",
mapping.size.Int(), len(remainder)))
return
}
map_bytes := remainder[:mapping.size.Int()]
remainder = remainder[mapping.size.Int():]
if len(remainder) == 0 {

View File

@ -1,6 +1,7 @@
package data
import (
"bytes"
"errors"
"sort"
@ -11,20 +12,37 @@ import (
type MappingValues [][2]I2PString
func (m MappingValues) Get(key I2PString) I2PString {
keyBytes, _ := key.Data()
if key == nil {
return nil
}
keyBytes, err := key.Data()
if err != nil {
return nil
}
log.WithFields(logrus.Fields{
"key": string(keyBytes),
}).Debug("Searching for key in MappingValues")
for _, pair := range m {
kb, _ := pair[0][0:].Data()
if pair[0] == nil {
continue
}
kb, err := pair[0].Data()
if err != nil {
continue
}
if kb == keyBytes {
data, _ := pair[1].Data()
log.WithFields(logrus.Fields{
"key": string(keyBytes),
"value": string(pair[1][1:]),
"value": string(data),
}).Debug("Found matching key in MappingValues")
return pair[1]
}
}
log.WithFields(logrus.Fields{
"key": string(keyBytes),
}).Debug("Key not found in MappingValues")
@ -64,10 +82,15 @@ func ValuesToMapping(values MappingValues) *Mapping {
// 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
data1, err1 := values[i][0].Data()
data2, err2 := values[j][0].Data()
// Handle error cases by treating them as "less than"
if err1 != nil || err2 != nil {
return err1 == nil
}
return bytes.Compare([]byte(data1), []byte(data2)) < 0
})
}
@ -217,6 +240,6 @@ func ReadMappingValues(remainder []byte, map_length Integer) (values *MappingVal
"remainder_length": len(remainder_bytes),
"error_count": len(errs),
}).Debug("Finished reading MappingValues")
remainder_bytes = remainder
return
}

View File

@ -2,6 +2,7 @@ package data
import (
"fmt"
"reflect"
"testing"
)
@ -45,3 +46,41 @@ func TestMappingOrderSortsValuesThenKeys(t *testing.T) {
}
}
}
func TestMappingValuesEdgeCases(t *testing.T) {
k1, _ := ToI2PString("test")
tests := []struct {
name string
mv MappingValues
key I2PString
want I2PString
}{
{
name: "nil key",
mv: MappingValues{},
key: nil,
want: nil,
},
{
name: "empty mapping",
mv: MappingValues{},
key: k1,
want: nil,
},
{
name: "nil value in pair",
mv: MappingValues{{k1, nil}},
key: k1,
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.mv.Get(tt.key)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MappingValues.Get() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -134,6 +134,11 @@ func ToI2PString(data string) (str I2PString, err error) {
// 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) {
if len(data) == 0 {
err = errors.New("data slice is empty")
log.WithError(err).Error("Passed data with len == 0")
return
}
log.WithFields(logrus.Fields{
"input_length": len(data),
}).Debug("Reading I2PString from bytes")
@ -143,6 +148,11 @@ func ReadI2PString(data []byte) (str I2PString, remainder []byte, err error) {
return
}
data_len := length.Int() + 1
if data_len > len(data) {
err = fmt.Errorf("I2PString length %d exceeds available data %d", data_len-1, len(data)-1)
log.WithError(err).Error("Failed to read I2PString")
return
}
str = data[:data_len]
remainder = data[data_len:]
l, err := str.Length()

View File

@ -38,7 +38,8 @@ type Destination struct {
func (destination Destination) Base32Address() (str string) {
log.Debug("Generating Base32 address for Destination")
dest := destination.KeysAndCert.KeyCertificate.Bytes()
cert := destination.KeysAndCert.Certificate()
dest := cert.Bytes()
hash := crypto.SHA256(dest)
str = strings.Trim(base32.EncodeToString(hash[:]), "=")
str = str + ".b32.i2p"
@ -54,7 +55,8 @@ func (destination Destination) Base32Address() (str string) {
func (destination Destination) Base64() string {
log.Debug("Generating Base64 address for Destination")
dest := destination.KeysAndCert.KeyCertificate.Bytes()
cert := destination.KeysAndCert.Certificate()
dest := cert.Bytes()
base64Address := base64.EncodeToString(dest)
log.WithFields(logrus.Fields{

View File

@ -5,7 +5,7 @@ import common "github.com/go-i2p/go-i2p/lib/common/router_identity"
func Fuzz(data []byte) int {
router_identity, _, _ := common.ReadRouterIdentity(data)
router_identity.Certificate()
router_identity.PublicKey()
router_identity.SigningPublicKey()
// router_identity.publicKey()
// router_identity.signingPublicKey()
return 0
}

View File

@ -66,7 +66,7 @@ const (
KEYCERT_MIN_SIZE = 7
)
// SigningPublicKey sizes for Signing Key Types
// signingPublicKey sizes for Signing Key Types
const (
KEYCERT_SIGN_DSA_SHA1_SIZE = 128
KEYCERT_SIGN_P256_SIZE = 64
@ -79,7 +79,7 @@ const (
KEYCERT_SIGN_ED25519PH_SIZE = 32
)
// PublicKey sizes for Public Key Types
// publicKey sizes for Public Key Types
const (
KEYCERT_CRYPTO_ELG_SIZE = 256
KEYCERT_CRYPTO_P256_SIZE = 64
@ -106,51 +106,51 @@ func (key_certificate KeyCertificate) Data() ([]byte, error) {
data := key_certificate.Certificate.RawBytes()
log.WithFields(logrus.Fields{
"data_length": len(data),
}).Debug("Retrieved raw data from KeyCertificate")
}).Debug("Retrieved raw data from keyCertificate")
return key_certificate.Certificate.RawBytes(), nil
}
// SigningPublicKeyType returns the SigningPublicKey type as a Go integer.
func (key_certificate KeyCertificate) SigningPublicKeyType() (signing_pubkey_type int) {
signing_pubkey_type = key_certificate.spkType.Int()
log.WithFields(logrus.Fields{
"signing_pubkey_type": signing_pubkey_type,
}).Debug("Retrieved SigningPublicKey type")
return key_certificate.spkType.Int()
// SigningPublicKeyType returns the signingPublicKey type as a Go integer.
func (key_certificate KeyCertificate) SigningPublicKeyType() int {
spk_type := key_certificate.spkType.Int()
log.WithFields(logrus.Fields{
"signing_pubkey_type": spk_type,
}).Debug("Retrieved signingPublicKey type")
return spk_type
}
// PublicKeyType returns the PublicKey type as a Go integer.
func (key_certificate KeyCertificate) PublicKeyType() (pubkey_type int) {
pubkey_type = key_certificate.cpkType.Int()
log.WithFields(logrus.Fields{
"pubkey_type": pubkey_type,
}).Debug("Retrieved PublicKey type")
return key_certificate.cpkType.Int()
func (key_certificate KeyCertificate) CryptoSize() int {
switch key_certificate.PublicKeyType() {
case KEYCERT_CRYPTO_ELG:
return KEYCERT_CRYPTO_ELG_SIZE
case KEYCERT_CRYPTO_P256:
return KEYCERT_CRYPTO_P256_SIZE
case KEYCERT_CRYPTO_P384:
return KEYCERT_CRYPTO_P384_SIZE
case KEYCERT_CRYPTO_P521:
return KEYCERT_CRYPTO_P521_SIZE
case KEYCERT_CRYPTO_X25519:
return KEYCERT_CRYPTO_X25519_SIZE
default:
return 0
}
}
// 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) {
log.WithFields(logrus.Fields{
"input_length": len(data),
}).Debug("Constructing PublicKey from KeyCertificate")
key_type := key_certificate.PublicKeyType()
if err != nil {
return
}
data_len := len(data)
if data_len < key_certificate.CryptoSize() {
log.WithFields(logrus.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:
log.WithFields(logrus.Fields{
"input_length": len(data),
}).Debug("Constructing publicKey from keyCertificate")
key_type := key_certificate.PublicKeyType()
data_len := len(data)
if data_len < key_certificate.CryptoSize() {
return nil, errors.New("error constructing public key: not enough data")
}
// Implementation missing here - needs to construct appropriate key type
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
@ -160,13 +160,25 @@ func (key_certificate KeyCertificate) ConstructPublicKey(data []byte) (public_ke
copy(ed25519_key[:], data[KEYCERT_PUBKEY_SIZE-KEYCERT_CRYPTO_ELG_SIZE:KEYCERT_PUBKEY_SIZE])
public_key = ed25519_key
log.Debug("Constructed Ed25519PublicKey")
default:
log.WithFields(logrus.Fields{
"key_type": key_type,
}).Warn("Unknown public key type")
}
case KEYCERT_CRYPTO_P256:
//return crypto.CreatePublicKey(data[:KEYCERT_CRYPTO_P256_SIZE])
case KEYCERT_CRYPTO_P384:
//return crypto.CreatePublicKey(data[:KEYCERT_CRYPTO_P384_SIZE])
case KEYCERT_CRYPTO_P521:
//return crypto.CreatePublicKey(data[:KEYCERT_CRYPTO_P521_SIZE])
default:
return nil, errors.New("error constructing public key: unknown key type")
}
return nil, errors.New("error constructing public key: unknown key type")
}
return
// PublicKeyType returns the publicKey type as a Go integer.
func (key_certificate KeyCertificate) PublicKeyType() int {
pk_type := key_certificate.cpkType.Int()
log.WithFields(logrus.Fields{
"pubkey_type": pk_type,
}).Debug("Retrieved publicKey type")
return pk_type
}
// ConstructSigningPublicKey returns a SingingPublicKey constructed using any excess data that may be stored in the KeyCertificate.
@ -174,7 +186,7 @@ func (key_certificate KeyCertificate) ConstructPublicKey(data []byte) (public_ke
func (key_certificate KeyCertificate) ConstructSigningPublicKey(data []byte) (signing_public_key crypto.SigningPublicKey, err error) {
log.WithFields(logrus.Fields{
"input_length": len(data),
}).Debug("Constructing SigningPublicKey from KeyCertificate")
}).Debug("Constructing signingPublicKey from keyCertificate")
signing_key_type := key_certificate.PublicKeyType()
if err != nil {
return
@ -182,7 +194,7 @@ func (key_certificate KeyCertificate) ConstructSigningPublicKey(data []byte) (si
data_len := len(data)
if data_len < key_certificate.SignatureSize() {
log.WithFields(logrus.Fields{
"at": "(KeyCertificate) ConstructSigningPublicKey",
"at": "(keyCertificate) ConstructSigningPublicKey",
"data_len": data_len,
"required_len": KEYCERT_SPK_SIZE,
"reason": "not enough data",
@ -247,7 +259,7 @@ func (key_certificate KeyCertificate) ConstructSigningPublicKey(data []byte) (si
return
}
// SignatureSize return the size of a Signature corresponding to the Key Certificate's SigningPublicKey type.
// SignatureSize return the size of a Signature corresponding to the Key Certificate's signingPublicKey type.
func (key_certificate KeyCertificate) SignatureSize() (size int) {
sizes := map[int]int{
KEYCERT_SIGN_DSA_SHA1: KEYCERT_SIGN_DSA_SHA1_SIZE,
@ -269,31 +281,13 @@ func (key_certificate KeyCertificate) SignatureSize() (size int) {
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()
size = sizes[int(key_type)]
log.WithFields(logrus.Fields{
"key_type": key_type,
"crypto_size": size,
}).Debug("Retrieved crypto size")
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) {
log.WithFields(logrus.Fields{
"input_length": len(bytes),
}).Debug("Creating new KeyCertificate")
}).Debug("Creating new keyCertificate")
var certificate Certificate
certificate, remainder, err = ReadCertificate(bytes)
@ -301,10 +295,14 @@ func NewKeyCertificate(bytes []byte) (key_certificate *KeyCertificate, remainder
log.WithError(err).Error("Failed to read Certificate")
return
}
if certificate.Type() != 5 { // Key certificate type must be 5
return nil, nil, errors.New("error parsing key certificate: invalid certificate type")
}
if len(bytes) < KEYCERT_MIN_SIZE {
log.WithError(err).Error("keyCertificate data too short")
err = errors.New("error parsing key certificate: not enough data")
remainder = bytes[KEYCERT_MIN_SIZE:]
log.WithError(err).Error("KeyCertificate data too short")
}
key_certificate = &KeyCertificate{
Certificate: certificate,
@ -320,20 +318,20 @@ func NewKeyCertificate(bytes []byte) (key_certificate *KeyCertificate, remainder
"spk_type": key_certificate.spkType.Int(),
"cpk_type": key_certificate.cpkType.Int(),
"remainder_length": len(remainder),
}).Debug("Successfully created new KeyCertificate")
}).Debug("Successfully created new keyCertificate")
return
}
// KeyCertificateFromCertificate returns a *KeyCertificate from a *Certificate.
func KeyCertificateFromCertificate(certificate Certificate) *KeyCertificate {
log.Debug("Creating KeyCertificate from Certificate")
log.Debug("Creating keyCertificate from Certificate")
// k, _, _ := NewKeyCertificate(certificate.RawBytes())
k, _, err := NewKeyCertificate(certificate.RawBytes())
if err != nil {
log.WithError(err).Error("Failed to create KeyCertificate from Certificate")
log.WithError(err).Error("Failed to create keyCertificate from Certificate")
} else {
log.Debug("Successfully created KeyCertificate from Certificate")
log.Debug("Successfully created keyCertificate from Certificate")
}
return k
}

View File

@ -35,7 +35,7 @@ func TestPublicKeyTypeReturnsCorrectInteger(t *testing.T) {
key_cert, _, err := NewKeyCertificate([]byte{0x05, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03})
pk_type := key_cert.PublicKeyType()
assert.Nil(err, "PublicKey() returned error with valid data")
assert.Nil(err, "publicKey() returned error with valid data")
assert.Equal(pk_type, KEYCERT_SIGN_P521, "PublicKeyType() did not return correct typec")
}
@ -94,7 +94,7 @@ func TestConstructSigningPublicKeyWithDSASHA1(t *testing.T) {
spk, err := key_cert.ConstructSigningPublicKey(data)
assert.Nil(err, "ConstructSigningPublicKey() with DSA SHA1 returned error with valid data")
assert.Equal(spk.Len(), KEYCERT_SIGN_DSA_SHA1_SIZE, "ConstructSigningPublicKey() with DSA SHA1 returned incorrect SigningPublicKey length")
assert.Equal(spk.Len(), KEYCERT_SIGN_DSA_SHA1_SIZE, "ConstructSigningPublicKey() with DSA SHA1 returned incorrect signingPublicKey length")
}
func TestConstructSigningPublicKeyWithP256(t *testing.T) {
@ -105,7 +105,7 @@ func TestConstructSigningPublicKeyWithP256(t *testing.T) {
spk, err := key_cert.ConstructSigningPublicKey(data)
assert.Nil(err, "ConstructSigningPublicKey() with P256 returned err on valid data")
assert.Equal(spk.Len(), KEYCERT_SIGN_P256_SIZE, "ConstructSigningPublicKey() with P256 returned incorrect SigningPublicKey length")
assert.Equal(spk.Len(), KEYCERT_SIGN_P256_SIZE, "ConstructSigningPublicKey() with P256 returned incorrect signingPublicKey length")
}
func TestConstructSigningPublicKeyWithP384(t *testing.T) {
@ -116,7 +116,7 @@ func TestConstructSigningPublicKeyWithP384(t *testing.T) {
spk, err := key_cert.ConstructSigningPublicKey(data)
assert.Nil(err, "ConstructSigningPublicKey() with P384 returned err on valid data")
assert.Equal(spk.Len(), KEYCERT_SIGN_P384_SIZE, "ConstructSigningPublicKey() with P384 returned incorrect SigningPublicKey length")
assert.Equal(spk.Len(), KEYCERT_SIGN_P384_SIZE, "ConstructSigningPublicKey() with P384 returned incorrect signingPublicKey length")
}
func TestConstructSigningPublicKeyWithP521(t *testing.T) {
@ -127,5 +127,5 @@ func TestConstructSigningPublicKeyWithP521(t *testing.T) {
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")
assert.Equal(spk.Len(), KEYCERT_SIGN_P521_SIZE, "ConstructSigningPublicKey() with P521 returned incorrect signingPublicKey length")
}

View File

@ -2,6 +2,7 @@
package keys_and_cert
import (
"crypto/rand"
"errors"
"github.com/go-i2p/go-i2p/lib/util/logger"
@ -30,7 +31,7 @@ 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.
A publicKey followed by a signingPublicKey and then a Certificate.
+----+----+----+----+----+----+----+----+
| public_key |
@ -55,14 +56,14 @@ A PublicKey followed by a SigningPublicKey and then a Certificate.
| certificate |
+----+----+----+-//
public_key :: PublicKey (partial or full)
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)
signing__key :: signingPublicKey (partial or full)
length -> 128 bytes or as specified in key certificate
padding length + signing_key length == 128 bytes
@ -76,34 +77,41 @@ total length: 387+ bytes
//
// https://geti2p.net/spec/common-structures#keysandcert
type KeysAndCert struct {
KeyCertificate *KeyCertificate
keyCertificate *KeyCertificate
publicKey crypto.PublicKey
padding []byte
Padding []byte
signingPublicKey crypto.SigningPublicKey
}
// Bytes returns the entire KeyCertificate in []byte form, trims payload to specified length.
// Bytes returns the entire keyCertificate in []byte form, trims payload to specified length.
func (keys_and_cert KeysAndCert) Bytes() []byte {
bytes := keys_and_cert.KeyCertificate.Bytes()
bytes := keys_and_cert.publicKey.Bytes()
bytes = append(bytes, keys_and_cert.Padding...)
bytes = append(bytes, keys_and_cert.signingPublicKey.Bytes()...)
bytes = append(bytes, keys_and_cert.keyCertificate.Bytes()...)
log.WithFields(logrus.Fields{
"bytes_length": len(bytes),
"bytes_length": len(bytes),
"pk_bytes_length": len(keys_and_cert.publicKey.Bytes()),
"padding_bytes_length": len(keys_and_cert.Padding),
"spk_bytes_length": len(keys_and_cert.signingPublicKey.Bytes()),
"cert_bytes_length": len(keys_and_cert.keyCertificate.Bytes()),
}).Debug("Retrieved bytes from KeysAndCert")
return bytes
}
// PublicKey returns the public key as a crypto.PublicKey.
// publicKey returns the public key as a crypto.publicKey.
func (keys_and_cert *KeysAndCert) PublicKey() (key crypto.PublicKey) {
return keys_and_cert.publicKey
}
// SigningPublicKey returns the signing public key.
// signingPublicKey returns the signing public key.
func (keys_and_cert *KeysAndCert) SigningPublicKey() (signing_public_key crypto.SigningPublicKey) {
return keys_and_cert.signingPublicKey
}
// Certfificate returns the certificate.
func (keys_and_cert *KeysAndCert) Certificate() (cert Certificate) {
return keys_and_cert.KeyCertificate.Certificate
return keys_and_cert.keyCertificate.Certificate
}
// ReadKeysAndCert creates a new *KeysAndCert from []byte using ReadKeysAndCert.
@ -123,7 +131,7 @@ func ReadKeysAndCert(data []byte) (keys_and_cert KeysAndCert, remainder []byte,
"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:])
keys_and_cert.keyCertificate, remainder, _ = NewKeyCertificate(data[KEYS_AND_CERT_DATA_SIZE:])
return
} else if data_len < KEYS_AND_CERT_DATA_SIZE {
log.WithFields(logrus.Fields{
@ -135,33 +143,110 @@ func ReadKeysAndCert(data []byte) (keys_and_cert KeysAndCert, remainder []byte,
err = errors.New("error parsing KeysAndCert: data is smaller than minimum valid size")
return
}
keys_and_cert.KeyCertificate, remainder, err = NewKeyCertificate(data[KEYS_AND_CERT_DATA_SIZE:])
keys_and_cert.keyCertificate, remainder, err = NewKeyCertificate(data[KEYS_AND_CERT_DATA_SIZE:])
if err != nil {
log.WithError(err).Error("Failed to create KeyCertificate")
log.WithError(err).Error("Failed to create keyCertificate")
return
}
// 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()])
keys_and_cert.publicKey, err = keys_and_cert.keyCertificate.ConstructPublicKey(data[:keys_and_cert.keyCertificate.CryptoSize()])
if err != nil {
log.WithError(err).Error("Failed to construct PublicKey")
log.WithError(err).Error("Failed to construct publicKey")
return
}
keys_and_cert.signingPublicKey, err = keys_and_cert.KeyCertificate.ConstructSigningPublicKey(data[KEYS_AND_CERT_DATA_SIZE-keys_and_cert.KeyCertificate.SignatureSize() : KEYS_AND_CERT_DATA_SIZE])
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 {
log.WithError(err).Error("Failed to construct SigningPublicKey")
log.WithError(err).Error("Failed to construct signingPublicKey")
return
}
padding := data[KEYS_AND_CERT_PUBKEY_SIZE : KEYS_AND_CERT_DATA_SIZE-KEYS_AND_CERT_SPK_SIZE]
keys_and_cert.padding = padding
keys_and_cert.Padding = padding
log.WithFields(logrus.Fields{
"public_key_type": keys_and_cert.KeyCertificate.PublicKeyType(),
"signing_public_key_type": keys_and_cert.KeyCertificate.SigningPublicKeyType(),
"public_key_type": keys_and_cert.keyCertificate.PublicKeyType(),
"signing_public_key_type": keys_and_cert.keyCertificate.SigningPublicKeyType(),
"padding_length": len(padding),
"remainder_length": len(remainder),
}).Debug("Successfully read KeysAndCert")
return
}
// NewKeysAndCert creates a new KeysAndCert instance with the provided parameters.
// It validates the sizes of the provided keys and padding before assembling the struct.
func NewKeysAndCert(
keyCertificate *KeyCertificate,
publicKey crypto.PublicKey,
padding []byte,
signingPublicKey crypto.SigningPublicKey,
) (*KeysAndCert, error) {
log.Debug("Creating new KeysAndCert with provided parameters")
// 1. Validate keyCertificate
if keyCertificate == nil {
log.Error("KeyCertificate is nil")
return nil, errors.New("KeyCertificate cannot be nil")
}
// 2. Validate publicKey size
if publicKey.Len() != KEYS_AND_CERT_PUBKEY_SIZE {
log.WithFields(logrus.Fields{
"expected_size": KEYS_AND_CERT_PUBKEY_SIZE,
"actual_size": publicKey.Len(),
}).Error("Invalid publicKey size")
return nil, errors.New("publicKey has an invalid size")
}
/*
// 3. Validate signingPublicKey size
if signingPublicKey.Len() != KEYS_AND_CERT_SPK_SIZE {
log.WithFields(logrus.Fields{
"expected_size": KEYS_AND_CERT_SPK_SIZE,
"actual_size": signingPublicKey.Len(),
}).Error("Invalid signingPublicKey size")
return nil, errors.New("signingPublicKey has an invalid size")
}
*/
// 4. Validate padding size
publicKeyLength := publicKey.Len()
signingPublicKeyLength := signingPublicKey.Len()
totalKeysSize := publicKeyLength + signingPublicKeyLength
expectedPaddingSize := KEYS_AND_CERT_DATA_SIZE - totalKeysSize
if len(padding) != expectedPaddingSize {
log.WithFields(logrus.Fields{
"expected_size": expectedPaddingSize,
"actual_size": len(padding),
}).Warn("Invalid padding size")
// generate some random padding and continue
padding = make([]byte, expectedPaddingSize)
_, err := rand.Read(padding)
if err != nil {
log.WithError(err).Error("Failed to generate random padding")
return nil, err
}
log.WithFields(logrus.Fields{
"expected_size": expectedPaddingSize,
"actual_size": len(padding),
}).Warn("Generated random padding")
}
// 5. Assemble KeysAndCert
keysAndCert := &KeysAndCert{
keyCertificate: keyCertificate,
publicKey: publicKey,
Padding: padding,
signingPublicKey: signingPublicKey,
}
log.WithFields(logrus.Fields{
"public_key_length": publicKey.Len(),
"signing_public_key_length": signingPublicKey.Len(),
"padding_length": len(padding),
}).Debug("Successfully created KeysAndCert")
return keysAndCert, nil
}

View File

@ -32,13 +32,13 @@ 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
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
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.
@ -100,10 +100,10 @@ finally a Signature of the previous bytes signed by the Destination's SigningPri
destination :: Destination
length -> >= 387 bytes
encryption_key :: PublicKey
encryption_key :: publicKey
length -> 256 bytes
signing_key :: SigningPublicKey
signing_key :: signingPublicKey
length -> 128 bytes or as specified in destination's key certificate
num :: Integer
@ -157,7 +157,7 @@ func (lease_set LeaseSet) PublicKey() (public_key crypto.ElgPublicKey, err error
remainder_len := len(remainder)
if remainder_len < LEASE_SET_PUBKEY_SIZE {
log.WithFields(logrus.Fields{
"at": "(LeaseSet) PublicKey",
"at": "(LeaseSet) publicKey",
"data_len": remainder_len,
"required_len": LEASE_SET_PUBKEY_SIZE,
"reason": "not enough data",
@ -167,7 +167,7 @@ func (lease_set LeaseSet) PublicKey() (public_key crypto.ElgPublicKey, err error
return
}
copy(public_key[:], remainder[:LEASE_SET_PUBKEY_SIZE])
log.Debug("Successfully retrieved PublicKey from LeaseSet")
log.Debug("Successfully retrieved publicKey from LeaseSet")
return
}
@ -200,11 +200,11 @@ func (lease_set LeaseSet) SigningKey() (signing_public_key crypto.SigningPublicK
}
if cert_len == 0 {
// No Certificate is present, return the LEASE_SET_SPK_SIZE byte
// SigningPublicKey space as legacy DSA SHA1 SigningPublicKey.
// 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
log.Debug("Retrieved legacy DSA SHA1 SigningPublicKey")
log.Debug("Retrieved legacy DSA SHA1 signingPublicKey")
} else {
// A Certificate is present in this LeaseSet's Destination
cert_type := cert.Type()
@ -216,17 +216,17 @@ func (lease_set LeaseSet) SigningKey() (signing_public_key crypto.SigningPublicK
lease_set[offset : offset+LEASE_SET_SPK_SIZE],
)
if err != nil {
log.WithError(err).Error("Failed to construct SigningPublicKey from KeyCertificate")
log.WithError(err).Error("Failed to construct signingPublicKey from keyCertificate")
} else {
log.Debug("Retrieved SigningPublicKey from KeyCertificate")
log.Debug("Retrieved signingPublicKey from keyCertificate")
}
} else {
// No Certificate is present, return the LEASE_SET_SPK_SIZE byte
// SigningPublicKey space as legacy DSA SHA1 SigningPublicKey.
// 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
log.Debug("Retrieved legacy DSA SHA1 SigningPublicKey (Certificate present but not Key Certificate)")
log.Debug("Retrieved legacy DSA SHA1 signingPublicKey (Certificate present but not Key Certificate)")
}
}
return
@ -358,7 +358,7 @@ func (lease_set LeaseSet) Verify() error {
//data := lease_set[:data_end]
//spk, _ := lease_set.
// Destination().
// SigningPublicKey()
// signingPublicKey()
//verifier, err := spk.NewVerifier()
//if err != nil {
// return err

View File

@ -61,7 +61,7 @@ func buildSignature(size int) []byte {
func buildFullLeaseSet(n int) LeaseSet {
lease_set_data := make([]byte, 0)
lease_set_data = append(lease_set_data, buildDestination().KeysAndCert.KeyCertificate.RawBytes()...)
lease_set_data = append(lease_set_data, buildDestination().KeysAndCert.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))

View File

@ -2,11 +2,13 @@
package router_address
import (
"encoding/binary"
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/sirupsen/logrus"
@ -152,14 +154,7 @@ func (router_address RouterAddress) Bytes() []byte {
bytes := make([]byte, 0)
bytes = append(bytes, router_address.TransportCost.Bytes()...)
bytes = append(bytes, router_address.ExpirationDate.Bytes()...)
strData, err := router_address.TransportType.Data()
if err != nil {
log.WithFields(logrus.Fields{
"error": err,
}).Error("RouterAddress.Bytes: error getting transport_style bytes")
} else {
bytes = append(bytes, strData...)
}
bytes = append(bytes, router_address.TransportType...)
bytes = append(bytes, router_address.TransportOptions.Data()...)
log.WithField("bytes_length", len(bytes)).Debug("Converted RouterAddress to bytes")
return bytes
@ -359,3 +354,57 @@ func ReadRouterAddress(data []byte) (router_address RouterAddress, remainder []b
}
return
}
// NewRouterAddress creates a new RouterAddress with the provided parameters.
// Returns a pointer to RouterAddress.
func NewRouterAddress(cost uint8, expiration time.Time, transportType string, options map[string]string) (*RouterAddress, error) {
log.Debug("Creating new RouterAddress")
// Create TransportCost as an Integer (1 byte)
transportCost, err := NewIntegerFromInt(int(cost), 1)
if err != nil {
log.WithError(err).Error("Failed to create TransportCost Integer")
return nil, err
}
// Create ExpirationDate as a Date
millis := expiration.UnixNano() / int64(time.Millisecond)
dateBytes := make([]byte, DATE_SIZE)
binary.BigEndian.PutUint64(dateBytes, uint64(millis))
expirationDate, _, err := NewDate(dateBytes)
if err != nil {
log.WithError(err).Error("Failed to create ExpirationDate")
return nil, err
}
// Create TransportType as an I2PString
transportTypeStr, err := ToI2PString(transportType)
if err != nil {
log.WithError(err).Error("Failed to create TransportType I2PString")
return nil, err
}
// Create TransportOptions as a Mapping
transportOptions, err := GoMapToMapping(options)
if err != nil {
log.WithError(err).Error("Failed to create TransportOptions Mapping")
return nil, err
}
// Create RouterAddress
ra := &RouterAddress{
TransportCost: transportCost,
ExpirationDate: expirationDate,
TransportType: transportTypeStr,
TransportOptions: transportOptions,
}
log.WithFields(logrus.Fields{
"cost": cost,
"expiration": expiration,
"transportType": transportType,
"options": options,
}).Debug("Successfully created new RouterAddress")
return ra, nil
}

View File

@ -2,7 +2,10 @@
package router_identity
import (
"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/common/keys_and_cert"
"github.com/go-i2p/go-i2p/lib/crypto"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/sirupsen/logrus"
)
@ -47,3 +50,30 @@ func ReadRouterIdentity(data []byte) (router_identity RouterIdentity, remainder
}).Debug("Successfully read RouterIdentity")
return
}
func NewRouterIdentity(publicKey crypto.PublicKey, signingPublicKey crypto.SigningPublicKey, cert certificate.Certificate, padding []byte) (*RouterIdentity, error) {
log.Debug("Creating new RouterIdentity")
// Step 1: Create keyCertificate from the provided certificate.
// Assuming NewKeyCertificate is a constructor that takes a Certificate and returns a keyCertificate.
keyCert := key_certificate.KeyCertificateFromCertificate(cert)
// Step 2: Create KeysAndCert instance.
keysAndCert, err := NewKeysAndCert(keyCert, publicKey, padding, signingPublicKey)
if err != nil {
log.WithError(err).Error("NewKeysAndCert failed.")
}
// Step 3: Initialize RouterIdentity with KeysAndCert.
routerIdentity := RouterIdentity{
KeysAndCert: *keysAndCert,
}
log.WithFields(logrus.Fields{
"public_key_type": keyCert.PublicKeyType(),
"signing_public_key_type": keyCert.SigningPublicKeyType(),
"padding_length": len(padding),
}).Debug("Successfully created RouterIdentity")
return &routerIdentity, nil
}

View File

@ -2,9 +2,14 @@
package router_info
import (
"encoding/binary"
"errors"
"github.com/go-i2p/go-i2p/lib/common/certificate"
"strconv"
"strings"
"time"
"github.com/go-i2p/go-i2p/lib/crypto"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/sirupsen/logrus"
@ -120,7 +125,7 @@ type RouterInfo struct {
// Bytes returns the RouterInfo as a []byte suitable for writing to a stream.
func (router_info RouterInfo) Bytes() (bytes []byte, err error) {
log.Debug("Converting RouterInfo to bytes")
bytes = append(bytes, router_info.router_identity.KeysAndCert.Bytes()...)
bytes = append(bytes, router_info.router_identity.Bytes()...)
bytes = append(bytes, router_info.published.Bytes()...)
bytes = append(bytes, router_info.size.Bytes()...)
for _, router_address := range router_info.addresses {
@ -133,17 +138,30 @@ func (router_info RouterInfo) Bytes() (bytes []byte, err error) {
return bytes, err
}
// Convert a byte slice into a string like [1, 2, 3] -> "1, 2, 3"
func bytesToString(bytes []byte) string {
str := "["
for i, b := range bytes {
str += strconv.Itoa(int(b))
if i < len(bytes)-1 {
str += ", "
}
}
str += "]"
return str
}
func (router_info RouterInfo) String() string {
log.Debug("Converting RouterInfo to string")
str := "Certificate: " + string(router_info.router_identity.KeysAndCert.Bytes())
str += "Published: " + string(router_info.published.Bytes())
str += "Addresses:" + string(router_info.size.Bytes())
str := "Certificate: " + bytesToString(router_info.router_identity.Bytes()) + "\n"
str += "Published: " + bytesToString(router_info.published.Bytes()) + "\n"
str += "Addresses:" + bytesToString(router_info.size.Bytes()) + "\n"
for index, router_address := range router_info.addresses {
str += "Address " + strconv.Itoa(index) + ": " + router_address.String()
str += "Address " + strconv.Itoa(index) + ": " + router_address.String() + "\n"
}
str += "Peer Size: " + string(router_info.peer_size.Bytes())
str += "Options: " + string(router_info.options.Data())
str += "Signature: " + string([]byte(*router_info.signature))
str += "Peer Size: " + bytesToString(router_info.peer_size.Bytes()) + "\n"
str += "Options: " + bytesToString(router_info.options.Data()) + "\n"
str += "Signature: " + bytesToString([]byte(*router_info.signature)) + "\n"
log.WithField("string_length", len(str)).Debug("Converted RouterInfo to string")
return str
}
@ -156,7 +174,9 @@ func (router_info *RouterInfo) RouterIdentity() *RouterIdentity {
// IndentHash returns the identity hash (sha256 sum) for this RouterInfo.
func (router_info *RouterInfo) IdentHash() Hash {
log.Debug("Calculating IdentHash for RouterInfo")
data, _ := router_info.RouterIdentity().KeyCertificate.Data()
// data, _ := router_info.RouterIdentity().keyCertificate.Data()
cert := router_info.RouterIdentity().KeysAndCert.Certificate()
data := cert.Data()
hash := HashData(data)
log.WithField("hash", hash).Debug("Calculated IdentHash for RouterInfo")
return HashData(data)
@ -216,7 +236,7 @@ func ReadRouterInfo(bytes []byte) (info RouterInfo, remainder []byte, err error)
"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")
err = errors.New("error parsing router info: not enough data to read identity")
return
}
info.published, remainder, err = NewDate(remainder)
@ -227,7 +247,7 @@ func ReadRouterInfo(bytes []byte) (info RouterInfo, remainder []byte, err error)
"required_len": DATE_SIZE,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router info: not enough data")
err = errors.New("error parsing router info: not enough data to read publish date")
}
info.size, remainder, err = NewInteger(remainder, 1)
if err != nil {
@ -248,7 +268,7 @@ func ReadRouterInfo(bytes []byte) (info RouterInfo, remainder []byte, err error)
//"required_len": ROUTER_ADDRESS_SIZE,
"reason": "not enough data",
}).Error("error parsing router address")
err = errors.New("error parsing router info: not enough data")
err = errors.New("error parsing router info: not enough data to read router addresses")
}
info.addresses = append(info.addresses, &address)
}
@ -272,7 +292,11 @@ func ReadRouterInfo(bytes []byte) (info RouterInfo, remainder []byte, err error)
}
err = errors.New("error parsing router info: " + estring)
}
info.signature, remainder, err = NewSignature(remainder)
sigType, err := certificate.GetSignatureTypeFromCertificate(info.router_identity.Certificate())
log.WithFields(logrus.Fields{
"sigType": sigType,
}).Debug("Got sigType")
info.signature, remainder, err = NewSignature(remainder, sigType)
if err != nil {
log.WithFields(logrus.Fields{
"at": "(RouterInfo) ReadRouterInfo",
@ -280,7 +304,7 @@ func ReadRouterInfo(bytes []byte) (info RouterInfo, remainder []byte, err error)
//"required_len": MAPPING_SIZE,
"reason": "not enough data",
}).Error("error parsing router info")
err = errors.New("error parsing router info: not enough data")
err = errors.New("error parsing router info: not enough data to read signature")
}
log.WithFields(logrus.Fields{
@ -293,6 +317,119 @@ func ReadRouterInfo(bytes []byte) (info RouterInfo, remainder []byte, err error)
return
}
// serializeWithoutSignature serializes the RouterInfo up to (but not including) the signature.
func (ri *RouterInfo) serializeWithoutSignature() []byte {
var bytes []byte
// Serialize RouterIdentity
bytes = append(bytes, ri.router_identity.Bytes()...)
// Serialize Published Date
bytes = append(bytes, ri.published.Bytes()...)
// Serialize Size
bytes = append(bytes, ri.size.Bytes()...)
// Serialize Addresses
for _, addr := range ri.addresses {
bytes = append(bytes, addr.Bytes()...)
}
// Serialize PeerSize (always zero)
bytes = append(bytes, ri.peer_size.Bytes()...)
// Serialize Options
bytes = append(bytes, ri.options.Data()...)
return bytes
}
func NewRouterInfo(
routerIdentity *RouterIdentity,
publishedTime time.Time,
addresses []*RouterAddress,
options map[string]string,
signingPrivateKey crypto.SigningPrivateKey,
sigType int,
) (*RouterInfo, error) {
log.Debug("Creating new RouterInfo")
// 1. Create Published Date
millis := publishedTime.UnixNano() / int64(time.Millisecond)
dateBytes := make([]byte, DATE_SIZE)
binary.BigEndian.PutUint64(dateBytes, uint64(millis))
publishedDate, _, err := ReadDate(dateBytes)
if err != nil {
log.WithError(err).Error("Failed to create Published Date")
return nil, err
}
// 2. Create Size Integer
sizeInt, err := NewIntegerFromInt(len(addresses), 1)
if err != nil {
log.WithError(err).Error("Failed to create Size Integer")
return nil, err
}
// 3. Create PeerSize Integer (always 0)
peerSizeInt, err := NewIntegerFromInt(0, 1)
if err != nil {
log.WithError(err).Error("Failed to create PeerSize Integer")
return nil, err
}
// 4. Convert options map to Mapping
mapping, err := GoMapToMapping(options)
if err != nil {
log.WithError(err).Error("Failed to convert options map to Mapping")
return nil, err
}
// 5. Assemble RouterInfo without signature
routerInfo := &RouterInfo{
router_identity: *routerIdentity,
published: &publishedDate,
size: sizeInt,
addresses: addresses,
peer_size: peerSizeInt,
options: mapping,
signature: nil, // To be set after signing
}
// 6. Serialize RouterInfo without signature
dataBytes := routerInfo.serializeWithoutSignature()
// 7. Compute signature over serialized data
signer, err := signingPrivateKey.NewSigner()
if err != nil {
log.WithError(err).Error("Failed to create new signer")
return nil, err
}
signatureBytes, err := signer.Sign(dataBytes)
if err != nil {
log.WithError(err).Error("Failed to sign")
}
// 8. Create Signature struct from signatureBytes
sig, _, err := ReadSignature(signatureBytes, sigType)
if err != nil {
log.WithError(err).Error("Failed to create Signature from signature bytes")
return nil, err
}
// 9. Attach signature to RouterInfo
routerInfo.signature = &sig
log.WithFields(logrus.Fields{
"router_identity": routerIdentity,
"published": publishedDate,
"address_count": len(addresses),
"options": options,
"signature": sig,
}).Debug("Successfully created RouterInfo")
return routerInfo, nil
}
func (router_info *RouterInfo) RouterCapabilities() string {
log.Debug("Retrieving RouterCapabilities")
str, err := ToI2PString("caps")

View File

@ -0,0 +1,112 @@
package router_info
import (
"bytes"
"crypto/rand"
"encoding/binary"
"github.com/go-i2p/go-i2p/lib/common/signature"
"testing"
"time"
"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/router_address"
"github.com/go-i2p/go-i2p/lib/common/router_identity"
"github.com/go-i2p/go-i2p/lib/crypto"
"golang.org/x/crypto/openpgp/elgamal"
)
func TestCreateRouterInfo(t *testing.T) {
// Generate signing key pair (Ed25519)
var ed25519_privkey crypto.Ed25519PrivateKey
_, err := (&ed25519_privkey).Generate()
if err != nil {
t.Fatalf("Failed to generate Ed25519 private key: %v\n", err)
}
ed25519_pubkey_raw, err := ed25519_privkey.Public()
if err != nil {
t.Fatalf("Failed to derive Ed25519 public key: %v\n", err)
}
ed25519_pubkey, ok := ed25519_pubkey_raw.(crypto.SigningPublicKey)
if !ok {
t.Fatalf("Failed to get SigningPublicKey from Ed25519 public key")
}
// Generate encryption key pair (ElGamal)
var elgamal_privkey elgamal.PrivateKey
err = crypto.ElgamalGenerate(&elgamal_privkey, rand.Reader)
if err != nil {
t.Fatalf("Failed to generate ElGamal private key: %v\n", err)
}
// Convert elgamal private key to crypto.ElgPrivateKey
var elg_privkey crypto.ElgPrivateKey
xBytes := elgamal_privkey.X.Bytes()
if len(xBytes) > 256 {
t.Fatalf("ElGamal private key X too large")
}
copy(elg_privkey[256-len(xBytes):], xBytes)
// Convert elgamal public key to crypto.ElgPublicKey
var elg_pubkey crypto.ElgPublicKey
yBytes := elgamal_privkey.PublicKey.Y.Bytes()
if len(yBytes) > 256 {
t.Fatalf("ElGamal public key Y too large")
}
copy(elg_pubkey[256-len(yBytes):], yBytes)
// Ensure that elg_pubkey implements crypto.PublicKey interface
var _ crypto.PublicKey = elg_pubkey
// Create KeyCertificate specifying key types
var payload bytes.Buffer
signingPublicKeyType, _ := data.NewIntegerFromInt(7, 2)
cryptoPublicKeyType, _ := data.NewIntegerFromInt(0, 2)
err = binary.Write(&payload, binary.BigEndian, signingPublicKeyType)
if err != nil {
t.Fatalf("Failed to write signing public key type to payload: %v\n", err)
}
err = binary.Write(&payload, binary.BigEndian, cryptoPublicKeyType)
if err != nil {
t.Fatalf("Failed to write crypto public key type to payload: %v\n", err)
}
// Create KeyCertificate specifying key types
cert, err := certificate.NewCertificateWithType(certificate.CERT_KEY, payload.Bytes())
if err != nil {
t.Fatalf("Failed to create new certificate: %v\n", err)
}
// Create RouterIdentity
routerIdentity, err := router_identity.NewRouterIdentity(elg_pubkey, ed25519_pubkey, *cert, nil)
if err != nil {
t.Fatalf("Failed to create router identity: %v\n", err)
}
// create some dummy addresses
options := map[string]string{}
routerAddress, err := router_address.NewRouterAddress(3, <-time.After(1*time.Second), "NTCP2", options)
if err != nil {
t.Fatalf("Failed to create router address: %v\n", err)
}
routerAddresses := []*router_address.RouterAddress{routerAddress}
// create router info
routerInfo, err := NewRouterInfo(routerIdentity, time.Now(), routerAddresses, nil, &ed25519_privkey, signature.SIGNATURE_TYPE_EDDSA_SHA512_ED25519)
if err != nil {
t.Fatalf("Failed to create router info: %v\n", err)
}
t.Run("Serialize and Deserialize RouterInfo", func(t *testing.T) {
routerInfoBytes, err := routerInfo.Bytes()
t.Log(len(routerInfoBytes), routerInfo.String(), routerInfoBytes)
if err != nil {
t.Fatalf("Failed to write RouterInfo to bytes: %v\n", err)
}
_, _, err = ReadRouterInfo(routerInfoBytes)
if err != nil {
t.Fatalf("Failed to read routerInfoBytes: %v\n", err)
}
})
}

View File

@ -2,6 +2,8 @@
package signature
import (
"fmt"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/sirupsen/logrus"
)
@ -22,6 +24,19 @@ const (
RedDSA_SHA512_Ed25519_SIZE = 64
)
const (
SIGNATURE_TYPE_DSA_SHA1 = 0
SIGNATURE_TYPE_ECDSA_SHA256_P256 = 1
SIGNATURE_TYPE_ECDSA_SHA384_P384 = 2
SIGNATURE_TYPE_ECDSA_SHA512_P521 = 3
SIGNATURE_TYPE_RSA_SHA256_2048 = 4
SIGNATURE_TYPE_RSA_SHA384_3072 = 5
SIGNATURE_TYPE_RSA_SHA512_4096 = 6
SIGNATURE_TYPE_EDDSA_SHA512_ED25519 = 7
SIGNATURE_TYPE_EDDSA_SHA512_ED25519PH = 8
SIGNATURE_TYPE_REDDSA_SHA512_ED25519 = 11
)
/*
[Signature]
Accurate for version 0.9.49
@ -39,30 +54,51 @@ DSA_SHA1. As of release 0.9.12, other types may be supported, depending on conte
// https://geti2p.net/spec/common-structures#signature
type Signature []byte
// ReadSignature returns Signature from a []byte.
// ReadSignature returns a 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
log.Warn("ReadSignature is not implemented")
// Returns an error if there is insufficient data to read the signature.
//
// Since the signature type and length are inferred from context (the type of key used),
// and are not explicitly stated, this function assumes the default signature type (DSA_SHA1)
// with a length of 40 bytes.
//
// If a different signature type is expected based on context, this function should be
// modified accordingly to handle the correct signature length.
func ReadSignature(data []byte, sigType int) (sig Signature, remainder []byte, err error) {
var sigLength int
switch sigType {
case SIGNATURE_TYPE_DSA_SHA1:
sigLength = DSA_SHA1_SIZE
case SIGNATURE_TYPE_EDDSA_SHA512_ED25519:
sigLength = EdDSA_SHA512_Ed25519_SIZE
default:
err = fmt.Errorf("unsupported signature type: %d", sigType)
return
}
if len(data) < sigLength {
err = fmt.Errorf("insufficient data to read signature: need %d bytes, have %d", sigLength, len(data))
log.WithError(err).Error("Failed to read Signature")
return
}
sig = data[:sigLength]
remainder = data[sigLength:]
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) {
func NewSignature(data []byte, sigType int) (signature *Signature, remainder []byte, err error) {
log.WithField("input_length", len(data)).Debug("Creating new Signature")
// sessionTag, remainder, err := ReadSignature(data)
sig, remainder, err := ReadSignature(data)
sig, remainder, err := ReadSignature(data, sigType)
if err != nil {
log.WithError(err).Error("Failed to read Signature")
return nil, remainder, err
}
session_tag = &sig
signature = &sig
log.WithFields(logrus.Fields{
"signature_length": len(sig),
"remainder_length": len(remainder),
}).Debug("Successfully created new Signature")
return
}

113
lib/config/config.go Normal file
View File

@ -0,0 +1,113 @@
package config
import (
"os"
"path/filepath"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
var (
CfgFile string
log = logger.GetGoI2PLogger()
)
const GOI2P_BASE_DIR = ".go-i2p"
func InitConfig() {
defaultConfigDir := filepath.Join(os.Getenv("HOME"), GOI2P_BASE_DIR)
defaultConfigFile := filepath.Join(defaultConfigDir, "config.yaml")
if CfgFile != "" {
// Use config file from the flag
viper.SetConfigFile(CfgFile)
} else {
// Create default config if it doesn't exist
if _, err := os.Stat(defaultConfigFile); os.IsNotExist(err) {
// Ensure directory exists
if err := os.MkdirAll(defaultConfigDir, 0o755); err != nil {
log.Fatalf("Could not create config directory: %s", err)
}
// Create default configuration
defaultConfig := struct {
BaseDir string `yaml:"base_dir"`
WorkingDir string `yaml:"working_dir"`
NetDB NetDbConfig `yaml:"netdb"`
Bootstrap BootstrapConfig `yaml:"bootstrap"`
}{
BaseDir: DefaultRouterConfig().BaseDir,
WorkingDir: DefaultRouterConfig().WorkingDir,
NetDB: *DefaultRouterConfig().NetDb,
Bootstrap: *DefaultRouterConfig().Bootstrap,
}
yamlData, err := yaml.Marshal(defaultConfig)
if err != nil {
log.Fatalf("Could not marshal default config: %s", err)
}
// Write default config file
if err := os.WriteFile(defaultConfigFile, yamlData, 0o644); err != nil {
log.Fatalf("Could not write default config file: %s", err)
}
log.Debugf("Created default configuration at: %s", defaultConfigFile)
}
// Set up viper to use the config file
viper.AddConfigPath(defaultConfigDir)
viper.SetConfigName("config")
viper.SetConfigType("yaml")
}
// Load defaults
setDefaults()
if err := viper.ReadInConfig(); err != nil {
log.Warnf("Error reading config file: %s", err)
} else {
log.Debugf("Using config file: %s", viper.ConfigFileUsed())
}
// Update RouterConfigProperties
UpdateRouterConfig()
}
func setDefaults() {
// Router defaults
viper.SetDefault("base_dir", DefaultRouterConfig().BaseDir)
viper.SetDefault("working_dir", DefaultRouterConfig().WorkingDir)
// NetDb defaults
viper.SetDefault("netdb.path", DefaultNetDbConfig.Path)
// Bootstrap defaults
viper.SetDefault("bootstrap.low_peer_threshold", DefaultBootstrapConfig.LowPeerThreshold)
viper.SetDefault("bootstrap.reseed_servers", []ReseedConfig{})
}
func UpdateRouterConfig() {
// Update Router configuration
RouterConfigProperties.BaseDir = viper.GetString("base_dir")
RouterConfigProperties.WorkingDir = viper.GetString("working_dir")
// Update NetDb configuration
RouterConfigProperties.NetDb = &NetDbConfig{
Path: viper.GetString("netdb.path"),
}
// Update Bootstrap configuration
var reseedServers []*ReseedConfig
if err := viper.UnmarshalKey("bootstrap.reseed_servers", &reseedServers); err != nil {
log.Warnf("Error parsing reseed servers: %s", err)
reseedServers = []*ReseedConfig{}
}
RouterConfigProperties.Bootstrap = &BootstrapConfig{
LowPeerThreshold: viper.GetInt("bootstrap.low_peer_threshold"),
ReseedServers: reseedServers,
}
}

View File

@ -26,11 +26,11 @@ func home() string {
}
func defaultBase() string {
return filepath.Join(home(), "go-i2p", "base")
return filepath.Join(home(), GOI2P_BASE_DIR, "base")
}
func defaultConfig() string {
return filepath.Join(home(), "go-i2p", "config")
return filepath.Join(home(), GOI2P_BASE_DIR, "config")
}
// defaults for router

View File

@ -95,6 +95,10 @@ type DSAVerifier struct {
type DSAPublicKey [128]byte
func (k DSAPublicKey) Bytes() []byte {
return k[:]
}
// create a new dsa verifier
func (k DSAPublicKey) NewVerifier() (v Verifier, err error) {
log.Debug("Creating new DSA verifier")

View File

@ -84,5 +84,5 @@ func BenchmarkDSASignVerify(b *testing.B) {
fail++
}
}
log.Infof("%d fails %d signs", fail, b.N)
log.Debugf("%d fails %d signs", fail, b.N)
}

View File

@ -73,6 +73,10 @@ func (k ECP256PublicKey) Len() int {
return len(k)
}
func (k ECP256PublicKey) Bytes() []byte {
return k[:]
}
func (k ECP256PublicKey) NewVerifier() (Verifier, error) {
log.Debug("Creating new P256 ECDSA verifier")
// return createECVerifier(elliptic.P256(), crypto.SHA256, k[:])
@ -88,6 +92,10 @@ type (
ECP384PrivateKey [48]byte
)
func (k ECP384PublicKey) Bytes() []byte {
return k[:]
}
func (k ECP384PublicKey) Len() int {
return len(k)
}
@ -107,6 +115,10 @@ type (
ECP521PrivateKey [66]byte
)
func (k ECP521PublicKey) Bytes() []byte {
return k[:]
}
func (k ECP521PublicKey) Len() int {
return len(k)
}

View File

@ -12,7 +12,10 @@ import (
"github.com/sirupsen/logrus"
)
var Ed25519EncryptTooBig = errors.New("failed to encrypt data, too big for Ed25519")
var (
Ed25519EncryptTooBig = errors.New("failed to encrypt data, too big for Ed25519")
ErrInvalidPublicKeySize = errors.New("failed to verify: invalid ed25519 public key size")
)
type Ed25519PublicKey []byte
@ -31,6 +34,10 @@ func (k Ed25519PublicKey) Len() int {
return len(k)
}
func (k Ed25519PublicKey) Bytes() []byte {
return k
}
func createEd25519PublicKey(data []byte) (k *ed25519.PublicKey) {
log.WithField("data_length", len(data)).Debug("Creating Ed25519 public key")
if len(data) == 256 {
@ -44,22 +51,36 @@ func createEd25519PublicKey(data []byte) (k *ed25519.PublicKey) {
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
}
// createEd25519Encryption initializes the Ed25519Encryption struct using the public key.
func createEd25519Encryption(pub *ed25519.PublicKey, randReader io.Reader) (*Ed25519Encryption, error) {
// Define p = 2^255 - 19
p := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(19))
// Validate public key length
if len(*pub) != ed25519.PublicKeySize {
log.WithField("pub_length", len(*pub)).Error("Invalid Ed25519 public key size")
return nil, ErrInvalidPublicKeySize
}
if err == nil {
enc = &Ed25519Encryption{}
}*/
log.Warn("createEd25519Encryption is not implemented")
return
// Convert public key bytes to big.Int
a := new(big.Int).SetBytes(*pub)
// Generate a random scalar b1 in [0, p)
b1, err := rand.Int(randReader, p)
if err != nil {
log.WithError(err).Error("Failed to generate b1 for Ed25519Encryption")
return nil, err
}
// Initialize Ed25519Encryption struct
enc := &Ed25519Encryption{
p: p,
a: a,
b1: b1,
}
log.Debug("Ed25519Encryption created successfully")
return enc, nil
}
type Ed25519Encryption struct {
@ -109,13 +130,18 @@ func (ed25519 *Ed25519Encryption) EncryptPadding(data []byte, zeroPadding bool)
func (elg Ed25519PublicKey) NewEncrypter() (enc Encrypter, err error) {
log.Debug("Creating new Ed25519 encrypter")
k := createEd25519PublicKey(elg[:])
if k == nil {
return nil, errors.New("invalid public key format")
}
enc, err = createEd25519Encryption(k, rand.Reader)
if err != nil {
log.WithError(err).Error("Failed to create Ed25519 encrypter")
} else {
log.Debug("Ed25519 encrypter created successfully")
return nil, err
}
return
log.Debug("Ed25519 encrypter created successfully")
return enc, nil
}
func (v *Ed25519Verifier) VerifyHash(h, sig []byte) (err error) {
@ -158,6 +184,42 @@ func (v *Ed25519Verifier) Verify(data, sig []byte) (err error) {
type Ed25519PrivateKey ed25519.PrivateKey
func (k Ed25519PrivateKey) NewDecrypter() (Decrypter, error) {
// TODO implement me
panic("implement me")
}
func (k Ed25519PrivateKey) NewSigner() (Signer, error) {
if len(k) != ed25519.PrivateKeySize {
return nil, errors.New("invalid ed25519 private key size")
}
return &Ed25519Signer{k: k}, nil
}
func (k Ed25519PrivateKey) Len() int {
return len(k)
}
func (k *Ed25519PrivateKey) Generate() (SigningPrivateKey, error) {
// Generate a new Ed25519 key pair
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
// Assign the generated private key to the receiver
*k = Ed25519PrivateKey(priv)
return k, nil
}
func (k Ed25519PrivateKey) Public() (SigningPublicKey, error) {
if len(k) != ed25519.PrivateKeySize {
return nil, errors.New("invalid ed25519 private key size")
}
// The public key is the first 32 bytes of the private key's seed
pubKey := k[32:]
return Ed25519PublicKey(pubKey), nil
}
type Ed25519Signer struct {
k []byte
}

View File

@ -237,6 +237,10 @@ func (elg ElgPublicKey) Len() int {
return len(elg)
}
func (elg ElgPublicKey) Bytes() []byte {
return elg[:]
}
func (elg ElgPublicKey) NewEncrypter() (enc Encrypter, err error) {
log.Debug("Creating new ElGamal encrypter")
k := createElgamalPublicKey(elg[:])

View File

@ -45,7 +45,7 @@ func BenchmarkElgDecrypt(b *testing.B) {
fails++
}
}
log.Infof("%d fails %d rounds", fails, b.N)
log.Debugf("%d fails %d rounds", fails, b.N)
}
func BenchmarkElgEncrypt(b *testing.B) {
@ -68,7 +68,7 @@ func BenchmarkElgEncrypt(b *testing.B) {
fails++
}
}
log.Infof("%d fails %d rounds", fails, b.N)
log.Debugf("%d fails %d rounds", fails, b.N)
}
func TestElg(t *testing.T) {

View File

@ -26,10 +26,11 @@ type SigningPublicKey interface {
NewVerifier() (Verifier, error)
// get the size of this public key
Len() int
Bytes() []byte
}
type PublicKey interface {
Len() int
Bytes() []byte
NewEncrypter() (Encrypter, error)
}

View File

@ -69,59 +69,55 @@ func (r Reseed) SingleReseed(uri string) ([]router_info.RouterInfo, error) {
if su3file.FileType == su3.ZIP {
if su3file.ContentType == su3.RESEED {
content, err := io.ReadAll(su3file.Content(""))
if err == nil {
content, err := io.ReadAll(su3file.Content(""))
if err == nil {
signature, err := io.ReadAll(su3file.Signature())
if err != nil {
return nil, err
}
log.Println("warning: this doesn't validate the signature yet", signature)
log.Warn("Doesn't validate the signature yet", logrus.Fields{"signature": signature})
}
zip := filepath.Join(config.RouterConfigProperties.NetDb.Path, "reseed.zip")
err = os.WriteFile(zip, content, 0o644)
signature, err := io.ReadAll(su3file.Signature())
if err != nil {
log.WithError(err).Error("Failed to write reseed zip file")
log.WithError(err).Error("Failed to read SU3 file signature")
return nil, err
}
// content is a zip file, unzip it and get the files
files, err := unzip.New().Extract(zip, config.RouterConfigProperties.NetDb.Path)
if err != nil {
log.WithError(err).Error("Failed to extract reseed zip file")
return nil, err
}
if len(files) <= 0 {
log.Error("Reseed appears to have no content")
return nil, fmt.Errorf("error: reseed appears to have no content")
}
log.WithField("file_count", len(files)).Debug("Successfully extracted reseed files")
var ris []router_info.RouterInfo
for _, f := range files {
riB, err := os.ReadFile(f)
if err != nil {
log.WithError(err).WithField("file", f).Warn("Failed to read router info file")
continue
}
ri, _, err := router_info.ReadRouterInfo(riB)
if err != nil {
log.WithError(err).WithField("file", f).Warn("Failed to parse router info")
continue
}
ris = append(ris, ri)
}
err = os.Remove(zip)
if err != nil {
log.WithError(err).Warn("Failed to remove reseed zip file")
}
log.WithField("router_info_count", len(ris)).Debug("Successfully processed reseed data")
return ris, err
} else {
log.WithError(err).Error("Failed to read SU3 file signature")
log.Println("warning: this doesn't validate the signature yet", signature)
log.Warn("Doesn't validate the signature yet", logrus.Fields{"signature": signature})
}
zip := filepath.Join(config.RouterConfigProperties.NetDb.Path, "reseed.zip")
err = os.WriteFile(zip, content, 0o644)
if err != nil {
log.WithError(err).Error("Failed to write reseed zip file")
return nil, err
}
// content is a zip file, unzip it and get the files
files, err := unzip.New().Extract(zip, config.RouterConfigProperties.NetDb.Path)
if err != nil {
log.WithError(err).Error("Failed to extract reseed zip file")
return nil, err
}
if len(files) <= 0 {
log.Error("Reseed appears to have no content")
return nil, fmt.Errorf("error: reseed appears to have no content")
}
log.WithField("file_count", len(files)).Debug("Successfully extracted reseed files")
var ris []router_info.RouterInfo
for _, f := range files {
riB, err := os.ReadFile(f)
if err != nil {
log.WithError(err).WithField("file", f).Warn("Failed to read router info file")
continue
}
ri, _, err := router_info.ReadRouterInfo(riB)
if err != nil {
log.WithError(err).WithField("file", f).Warn("Failed to parse router info")
continue
}
ris = append(ris, ri)
}
err = os.Remove(zip)
if err != nil {
log.WithError(err).Warn("Failed to remove reseed zip file")
}
log.WithField("router_info_count", len(ris)).Debug("Successfully processed reseed data")
return ris, err
}
}
log.Error("Undefined reseed error")

View File

@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
@ -31,7 +30,7 @@ type StdNetDB struct {
}
func NewStdNetDB(db string) StdNetDB {
log.WithField("db_path", db).Info("Creating new StdNetDB")
log.WithField("db_path", db).Debug("Creating new StdNetDB")
return StdNetDB{
DB: db,
RouterInfos: make(map[common.Hash]Entry),
@ -103,7 +102,7 @@ func (db *StdNetDB) Size() (routers int) {
log.WithError(err).Panic("Failed to recalculate NetDB size")
}
}
data, err = ioutil.ReadFile(db.cacheFilePath())
data, err = os.ReadFile(db.cacheFilePath())
if err == nil {
routers, err = strconv.Atoi(string(data))
if err != nil {
@ -189,7 +188,7 @@ func (db *StdNetDB) RecalculateSize() (err error) {
return err
})
if err == nil {
log.WithField("count", count).Info("Finished recalculating NetDB size")
log.WithField("count", count).Debug("Finished recalculating NetDB size")
str := fmt.Sprintf("%d", count)
var f *os.File
f, err = os.OpenFile(db.cacheFilePath(), os.O_CREATE|os.O_WRONLY, 0o600)
@ -286,7 +285,6 @@ func (db *StdNetDB) Ensure() (err error) {
func (db *StdNetDB) Create() (err error) {
mode := os.FileMode(0o700)
p := db.Path()
// log.Infof("Create network database in %s", p)
log.WithField("path", p).Debug("Creating network database directory")
// create root for skiplist
err = os.MkdirAll(p, mode)

View File

@ -20,17 +20,16 @@ type Router struct {
running bool
}
// create router with default configuration
func CreateRouter() (r *Router, err error) {
log.Debug("Creating router with default configuration")
cfg := config.RouterConfigProperties
r, err = FromConfig(cfg)
// CreateRouter creates a router with the provided configuration
func CreateRouter(cfg *config.RouterConfig) (*Router, error) {
log.Debug("Creating router with provided configuration")
r, err := FromConfig(cfg)
if err != nil {
log.WithError(err).Error("Failed to create router from default configuration")
log.WithError(err).Error("Failed to create router from configuration")
} else {
log.Debug("Router created successfully with default configuration")
log.Debug("Router created successfully with provided configuration")
}
return
return r, err
}
// create router from configuration

View File

@ -53,7 +53,7 @@ func fileRSAPubKey(t *testing.T, filename string) *rsa.PublicKey {
}
var pubKey *rsa.PublicKey
if k, ok := cert.PublicKey.(*rsa.PublicKey); !ok {
t.Fatalf("expected rsa.PublicKey from file %s", filename)
t.Fatalf("expected rsa.publicKey from file %s", filename)
} else {
pubKey = k
}

View File

@ -87,7 +87,7 @@ func (tmux *TransportMuxer) GetSession(routerInfo router_info.RouterInfo) (s Tra
continue
}
// we got a session
log.WithField("transport_index", i).Info("TransportMuxer: Successfully got session from transport")
log.WithField("transport_index", i).Debug("TransportMuxer: Successfully got session from transport")
return
}
}

300
lib/transport/noise/doc.md Normal file
View File

@ -0,0 +1,300 @@
# noise
## Overview
The `noise` package implements the Noise Protocol to establish secure, authenticated sessions over TCP. This package includes functions for session management, handshake initiation, packet encryption, decryption, and transport abstraction.
- [handshake.go](#handshakego)
- [i2np.go](#i2npgo)
- [incoming_handshake.go](#incoming_handshakego)
- [outgoing_handshake.go](#outgoing_handshakego)
- [noise_constants.go](#noise_constantsgo)
- [read_session.go](#read_sessiongo)
- [session.go](#sessiongo)
- [transport.go](#transportgo)
- [write_session.go](#write_sessiongo)
---
## handshake.go
Defines the `Handshake` function, which initiates the Noise handshake process for secure, authenticated sessions.
### Package
```go
package noise
```
### Imports
```go
import (
"sync"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/go-i2p/go-i2p/lib/common/router_info"
)
```
### Variables
- **`log`**: Logger instance for capturing debug and error messages related to the handshake process.
### Function: `Handshake`
#### Definition
```go
func (c *NoiseTransport) Handshake(routerInfo router_info.RouterInfo) error
```
#### Parameters
- `routerInfo`: Information about the router with which the handshake is established.
#### Returns
- `error`: Returns `nil` on success, or an error if the handshake fails.
#### Description
The `Handshake` function initiates an authenticated handshake with a router, establishing a secure session.
#### Workflow
1. **Logging Start**: Logs initiation of the handshake.
2. **Lock Mutex**: Locks `c.Mutex` to prevent concurrent access.
3. **Session Retrieval**: Calls `c.getSession(routerInfo)`.
4. **Condition Variable Setup**: Sets a `Cond` for the session.
5. **Outgoing Handshake**: Executes `RunOutgoingHandshake`.
6. **Completion Broadcast**: Broadcasts to waiting goroutines.
7. **Finalize and Unlock**: Logs success.
---
## i2np.go
Provides functions to queue and send I2NP messages using a `NoiseSession`.
### Package
```go
package noise
```
### Imports
```go
import "github.com/go-i2p/go-i2p/lib/i2np"
```
### Functions
#### `QueueSendI2NP`
Queues an I2NP message for sending.
```go
func (s *NoiseSession) QueueSendI2NP(msg i2np.I2NPMessage)
```
#### Parameters
- `msg`: The I2NP message.
---
#### `SendQueueSize`
Returns the size of the send queue.
```go
func (s *NoiseSession) SendQueueSize() int
```
---
#### `ReadNextI2NP`
Attempts to read the next I2NP message from the queue.
```go
func (s *NoiseSession) ReadNextI2NP() (i2np.I2NPMessage, error)
```
---
## incoming_handshake.go
Defines functions for the incoming (receiver) side of the handshake.
### Functions
#### `ComposeReceiverHandshakeMessage`
Creates a receiver handshake message using Noise patterns.
```go
func ComposeReceiverHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error)
```
- **`s`**: Static Diffie-Hellman key.
- **`rs`**: Remote static key.
- **`payload`**: Optional payload data.
- **`ePrivate`**: Private ephemeral key.
---
#### `RunIncomingHandshake`
Executes an incoming handshake process.
```go
func (c *NoiseSession) RunIncomingHandshake() error
```
- Initializes and sends the negotiation data and handshake message.
---
## outgoing_handshake.go
Defines functions for the outgoing (initiator) side of the handshake.
### Functions
#### `ComposeInitiatorHandshakeMessage`
Creates an initiator handshake message.
```go
func ComposeInitiatorHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error)
```
---
#### `RunOutgoingHandshake`
Executes the outgoing handshake process.
```go
func (c *NoiseSession) RunOutgoingHandshake() error
```
- Sends negotiation data and handshake message.
---
## noise_constants.go
Defines constants and utility functions for configuring Noise protocol parameters.
### Constants
```go
const (
NOISE_DH_CURVE25519 = 1
NOISE_CIPHER_CHACHAPOLY = 1
NOISE_HASH_SHA256 = 3
NOISE_PATTERN_XK = 11
uint16Size = 2
MaxPayloadSize = 65537
)
```
### Functions
#### `initNegotiationData`
Initializes negotiation data with default values.
```go
func initNegotiationData(negotiationData []byte) []byte
```
---
## read_session.go
Functions related to reading encrypted data in a Noise session.
### Functions
#### `Read`
Reads from the Noise session.
```go
func (c *NoiseSession) Read(b []byte) (int, error)
```
#### `decryptPacket`
Decrypts a packet.
```go
func (c *NoiseSession) decryptPacket(data []byte) (int, []byte, error)
```
---
## session.go
Defines the `NoiseSession` struct and associated methods for session management.
### Struct: `NoiseSession`
Defines session properties.
```go
type NoiseSession struct {
// Session properties here
}
```
---
## transport.go
Defines the `NoiseTransport` struct and its methods for session compatibility, accepting connections, etc.
### Struct: `NoiseTransport`
```go
type NoiseTransport struct {
sync.Mutex
router_identity.RouterIdentity
*noise.CipherState
Listener net.Listener
peerConnections map[data.Hash]transport.TransportSession
}
```
#### Methods
- `Compatible`: Checks compatibility.
- `Accept`: Accepts a connection.
- `Addr`: Returns the address.
- `SetIdentity`: Sets the router identity.
- `GetSession`: Obtains a session.
---
## write_session.go
Functions for writing encrypted data in a Noise session.
### Functions
#### `Write`
Writes data in a Noise session.
```go
func (c *NoiseSession) Write(b []byte) (int, error)
```
#### `encryptPacket`
Encrypts a packet.
```go
func (c *NoiseSession) encryptPacket(data []byte) (int, []byte, error)
```

View File

@ -5,15 +5,63 @@ import (
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"fmt"
"testing"
"github.com/go-i2p/go-i2p/lib/crypto"
"github.com/go-i2p/go-i2p/lib/transport/ntcp"
"github.com/go-i2p/go-i2p/lib/transport/obfs"
"github.com/flynn/noise"
"github.com/stretchr/testify/assert"
)
func (ns *NoiseSession) testEncryptPacket(plaintext []byte) (int, []byte, error) {
if ns.CipherState == nil {
return 0, nil, fmt.Errorf("CipherState is nil")
}
// Encrypt the data
ciphertext, err := ns.CipherState.Encrypt(nil, nil, plaintext)
if err != nil {
log.Fatalf("unimplemented\nerror:%v\n", err)
}
// Prepend the length of the ciphertext as a 2-byte big-endian value
packetLength := uint16(len(ciphertext))
packet := make([]byte, 2+len(ciphertext))
binary.BigEndian.PutUint16(packet[:2], packetLength)
copy(packet[2:], ciphertext)
return len(packet), packet, nil
}
func (ns *NoiseSession) testPacketDeux(packet []byte) (int, []byte, error) {
if ns.CipherState == nil {
return 0, nil, fmt.Errorf("CipherState is nil")
}
if len(packet) < 2 {
return 0, nil, fmt.Errorf("Packet too short to contain length prefix")
}
// Extract the length prefix
packetLength := binary.BigEndian.Uint16(packet[:2])
if len(packet[2:]) < int(packetLength) {
return 0, nil, fmt.Errorf("Packet data is shorter than indicated length")
}
ciphertext := packet[2 : 2+packetLength]
// Decrypt the data
plaintext, err := ns.CipherState.Decrypt(nil, nil, ciphertext)
if err != nil {
return 0, nil, err
}
return len(plaintext), plaintext, nil
}
func TestEncryptDecryptPacketOffline(t *testing.T) {
// Generate static keypairs
initiatorStatic, err := noise.DH25519.GenerateKeypair(rand.Reader)
@ -488,7 +536,7 @@ func TestEncryptDecryptPacketObfsOfflineWithFunc(t *testing.T) {
}
// Obfuscate Alice's ephemeral public key in message 1
obfuscatedMsg1, err := ntcp.ObfuscateEphemeralKey(msg1, aesKey)
obfuscatedMsg1, err := obfs.ObfuscateEphemeralKey(msg1, aesKey)
if err != nil {
t.Fatalf("Failed to obfuscate message 1: %v", err)
}
@ -498,7 +546,7 @@ func TestEncryptDecryptPacketObfsOfflineWithFunc(t *testing.T) {
// -------------------------------
// Deobfuscate Alice's ephemeral public key in message 1
deobfuscatedMsg1, err := ntcp.DeobfuscateEphemeralKey(obfuscatedMsg1, aesKey)
deobfuscatedMsg1, err := obfs.DeobfuscateEphemeralKey(obfuscatedMsg1, aesKey)
if err != nil {
t.Fatalf("Failed to deobfuscate message 1: %v", err)
}
@ -526,7 +574,7 @@ func TestEncryptDecryptPacketObfsOfflineWithFunc(t *testing.T) {
}
// Obfuscate Bob's ephemeral public key in message 2
obfuscatedMsg2, err := ntcp.ObfuscateEphemeralKey(msg2, aesKey)
obfuscatedMsg2, err := obfs.ObfuscateEphemeralKey(msg2, aesKey)
if err != nil {
t.Fatalf("Failed to obfuscate message 2: %v", err)
}
@ -536,7 +584,7 @@ func TestEncryptDecryptPacketObfsOfflineWithFunc(t *testing.T) {
// -------------------------------
// Deobfuscate Bob's ephemeral public key in message 2
deobfuscatedMsg2, err := ntcp.DeobfuscateEphemeralKey(obfuscatedMsg2, aesKey)
deobfuscatedMsg2, err := obfs.DeobfuscateEphemeralKey(obfuscatedMsg2, aesKey)
if err != nil {
t.Fatalf("Failed to deobfuscate message 2: %v", err)
}
@ -587,12 +635,12 @@ func TestEncryptDecryptPacketObfsOfflineWithFunc(t *testing.T) {
}
originalData := []byte("This is a test message.")
_, encryptedPacket, err := initiatorSession.encryptPacketDeux(originalData)
_, encryptedPacket, err := initiatorSession.testEncryptPacket(originalData)
if err != nil {
t.Fatalf("Encryption failed: %v", err)
}
_, decryptedData, err := responderSession.decryptPacketDeux(encryptedPacket)
_, decryptedData, err := responderSession.testPacketDeux(encryptedPacket)
if err != nil {
t.Fatalf("Decryption failed: %v", err)
}
@ -608,12 +656,12 @@ func TestEncryptDecryptPacketObfsOfflineWithFunc(t *testing.T) {
}
responseData := []byte("This is a response message.")
_, encryptedResponse, err := responderSession.encryptPacketDeux(responseData)
_, encryptedResponse, err := responderSession.testEncryptPacket(responseData)
if err != nil {
t.Fatalf("Responder encryption failed: %v", err)
}
_, decryptedResponse, err := initiatorSession.decryptPacketDeux(encryptedResponse)
_, decryptedResponse, err := initiatorSession.testPacketDeux(encryptedResponse)
if err != nil {
t.Fatalf("Initiator decryption failed: %v", err)
}

View File

@ -3,11 +3,79 @@ package noise
import (
"sync"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/flynn/noise"
)
type HandshakeState struct {
mutex sync.Mutex
ephemeral *noise.DHKey
pattern noise.HandshakePattern
handshakeComplete bool
HandKey noise.DHKey
*noise.HandshakeState
}
func NewHandshakeState(staticKey noise.DHKey, isInitiator bool) (*HandshakeState, error) {
hs := &HandshakeState{
pattern: noise.HandshakeXK,
}
config := noise.Config{
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherAESGCM, noise.HashSHA256),
Pattern: hs.pattern,
Initiator: isInitiator,
StaticKeypair: staticKey,
}
protocol, err := noise.NewHandshakeState(config)
if err != nil {
return nil, err
}
hs.HandshakeState = protocol
return hs, nil
}
// GenerateEphemeral creates the ephemeral keypair that will be used in handshake
// This needs to be separate so NTCP2 can obfuscate it
func (h *HandshakeState) GenerateEphemeral() (*noise.DHKey, error) {
h.mutex.Lock()
defer h.mutex.Unlock()
dhKey, err := noise.DH25519.GenerateKeypair(nil)
if err != nil {
return nil, err
}
h.ephemeral = &dhKey
return &dhKey, nil
}
// SetEphemeral allows setting a potentially modified ephemeral key
// This is needed for NTCP2's obfuscation layer
func (h *HandshakeState) SetEphemeral(key *noise.DHKey) error {
h.mutex.Lock()
defer h.mutex.Unlock()
h.ephemeral = key
return nil
}
func (h *HandshakeState) WriteMessage(payload []byte) ([]byte, *noise.CipherState, *noise.CipherState, error) {
h.mutex.Lock()
defer h.mutex.Unlock()
return h.HandshakeState.WriteMessage(nil, payload)
}
func (h *HandshakeState) ReadMessage(message []byte) ([]byte, *noise.CipherState, *noise.CipherState, error) {
h.mutex.Lock()
defer h.mutex.Unlock()
return h.HandshakeState.ReadMessage(nil, message)
}
var log = logger.GetGoI2PLogger()
func (c *NoiseTransport) Handshake(routerInfo router_info.RouterInfo) error {

View File

@ -3,61 +3,13 @@ package noise
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"github.com/sirupsen/logrus"
"github.com/flynn/noise"
"github.com/sirupsen/logrus"
)
func ComposeReceiverHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error) {
log.Debug("Starting ComposeReceiverHandshakeMessage")
if len(rs) != 0 && len(rs) != noise.DH25519.DHLen() {
log.WithField("rs_length", len(rs)).Error("Invalid remote static key length")
return nil, nil, nil, errors.New("only 32 byte curve25519 public keys are supported")
}
negData = make([]byte, 6)
copy(negData, initNegotiationData(nil))
pattern := noise.HandshakeXK
negData[5] = NOISE_PATTERN_XK
log.WithField("pattern", "XK").Debug("Noise pattern set")
var random io.Reader
if len(ePrivate) == 0 {
random = rand.Reader
log.Debug("Using crypto/rand as random source")
} else {
random = bytes.NewBuffer(ePrivate)
log.Debug("Using provided ePrivate as random source")
}
prologue := make([]byte, 2, uint16Size+len(negData))
binary.BigEndian.PutUint16(prologue, uint16(len(negData)))
prologue = append(prologue, negData...)
log.WithField("prologue_length", len(prologue)).Debug("Prologue created")
// prologue = append(initString, prologue...)
state, err = noise.NewHandshakeState(noise.Config{
StaticKeypair: s,
Initiator: false,
Pattern: pattern,
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256),
PeerStatic: rs,
Prologue: prologue,
Random: random,
})
if err != nil {
log.WithError(err).Error("Failed to create new handshake state")
return
}
log.WithField("message_length", len(msg)).Debug("Handshake message composed successfully")
// log.Debug("Handshake state created successfully")
padBuf := make([]byte, 2+len(payload))
copy(padBuf[2:], payload)
msg, _, _, err = state.WriteMessage(msg, padBuf)
return
}
func (c *NoiseSession) RunIncomingHandshake() error {
log.Debug("Starting incoming handshake")
@ -66,6 +18,9 @@ func (c *NoiseSession) RunIncomingHandshake() error {
log.WithError(err).Error("Failed to compose receiver handshake message")
return err
}
c.HandshakeState = &HandshakeState{
HandshakeState: state,
}
log.WithFields(logrus.Fields{
"negData_length": len(negData),
"msg_length": len(msg),
@ -86,3 +41,51 @@ func (c *NoiseSession) RunIncomingHandshake() error {
log.Debug("Incoming handshake completed successfully")
return nil
}
func ComposeReceiverHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error) {
log.Debug("Starting ComposeReceiverHandshakeMessage")
if len(rs) != 0 && len(rs) != noise.DH25519.DHLen() {
log.WithField("rs_length", len(rs)).Error("Invalid remote static key length")
return nil, nil, nil, errors.New("only 32 byte curve25519 public keys are supported")
}
negData = make([]byte, 6)
copy(negData, initNegotiationData(nil))
pattern := noise.HandshakeXK
negData[5] = NOISE_PATTERN_XK
var random io.Reader
if len(ePrivate) == 0 {
random = rand.Reader
log.Debug("Using crypto/rand as random source")
} else {
random = bytes.NewBuffer(ePrivate)
}
config := noise.Config{
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherAESGCM, noise.HashSHA256),
Pattern: pattern,
Initiator: false,
StaticKeypair: s,
Random: random,
}
state, err = noise.NewHandshakeState(config)
if err != nil {
return nil, nil, nil, err
}
// Write message 2, expecting no CipherStates yet
msg, cs0, cs1, err := state.WriteMessage(nil, payload)
if err != nil {
return nil, nil, nil, err
}
// Verify no CipherStates are returned yet
if cs0 != nil || cs1 != nil {
return nil, nil, nil, errors.New("unexpected cipher states in message 2")
}
return negData, msg, state, nil
}

View File

@ -3,7 +3,6 @@ package noise
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
@ -12,56 +11,6 @@ import (
"github.com/flynn/noise"
)
func ComposeInitiatorHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error) {
log.Debug("Starting ComposeInitiatorHandshakeMessage")
if len(rs) != 0 && len(rs) != noise.DH25519.DHLen() {
log.WithField("rs_length", len(rs)).Error("Invalid remote static key length")
return nil, nil, nil, errors.New("only 32 byte curve25519 public keys are supported")
}
negData = make([]byte, 6)
copy(negData, initNegotiationData(nil))
pattern := noise.HandshakeXK
negData[5] = NOISE_PATTERN_XK
log.WithField("pattern", "XK").Debug("Noise pattern set")
var random io.Reader
if len(ePrivate) == 0 {
random = rand.Reader
log.Debug("Using crypto/rand as random source")
} else {
random = bytes.NewBuffer(ePrivate)
log.Debug("Using provided ePrivate as random source")
}
prologue := make([]byte, 2, uint16Size+len(negData))
binary.BigEndian.PutUint16(prologue, uint16(len(negData)))
prologue = append(prologue, negData...)
log.WithField("prologue_length", len(prologue)).Debug("Prologue created")
// prologue = append(initString, prologue...)
state, err = noise.NewHandshakeState(noise.Config{
StaticKeypair: s,
Initiator: true,
Pattern: pattern,
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256),
PeerStatic: rs,
Prologue: prologue,
Random: random,
})
if err != nil {
log.WithError(err).Error("Failed to create new handshake state")
return
}
log.Debug("Handshake state created successfully")
padBuf := make([]byte, 2+len(payload))
copy(padBuf[2:], payload)
msg, _, _, err = state.WriteMessage(msg, padBuf)
if err != nil {
log.WithError(err).Error("Failed to write handshake message")
return
}
log.WithField("message_length", len(msg)).Debug("Handshake message composed successfully")
return
}
func (c *NoiseSession) RunOutgoingHandshake() error {
log.Debug("Starting outgoing handshake")
@ -74,6 +23,9 @@ func (c *NoiseSession) RunOutgoingHandshake() error {
"negData_length": len(negData),
"msg_length": len(msg),
}).Debug("Initiator handshake message composed")
c.HandshakeState = &HandshakeState{
HandshakeState: state,
}
if _, err = c.Write(negData); err != nil {
log.WithError(err).Error("Failed to write negotiation data")
@ -92,3 +44,49 @@ func (c *NoiseSession) RunOutgoingHandshake() error {
log.Debug("Outgoing handshake completed successfully")
return nil
}
func ComposeInitiatorHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error) {
log.Debug("Starting ComposeInitiatorHandshakeMessage")
if len(rs) != 0 && len(rs) != noise.DH25519.DHLen() {
return nil, nil, nil, errors.New("only 32 byte curve25519 public keys are supported")
}
negData = make([]byte, 6)
copy(negData, initNegotiationData(nil))
pattern := noise.HandshakeXK
negData[5] = NOISE_PATTERN_XK
var random io.Reader
if len(ePrivate) == 0 {
random = rand.Reader
} else {
random = bytes.NewBuffer(ePrivate)
}
config := noise.Config{
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherAESGCM, noise.HashSHA256),
Pattern: pattern,
Initiator: true,
StaticKeypair: s,
Random: random,
}
state, err = noise.NewHandshakeState(config)
if err != nil {
return nil, nil, nil, err
}
// Write message, expecting no CipherStates yet since this is message 1
msg, cs0, cs1, err := state.WriteMessage(nil, payload)
if err != nil {
return nil, nil, nil, err
}
// Verify no CipherStates are returned yet
if cs0 != nil || cs1 != nil {
return nil, nil, nil, errors.New("unexpected cipher states in message 1")
}
return negData, msg, state, nil
}

View File

@ -1,9 +1,7 @@
package noise
import (
"encoding/binary"
"errors"
"fmt"
"sync/atomic"
"github.com/sirupsen/logrus"
@ -86,33 +84,6 @@ func (c *NoiseSession) decryptPacket(data []byte) (int, []byte, error) {
//c.freeBlock(packet)
}
func (ns *NoiseSession) decryptPacketDeux(packet []byte) (int, []byte, error) {
if ns.CipherState == nil {
return 0, nil, fmt.Errorf("CipherState is nil")
}
if len(packet) < 2 {
return 0, nil, fmt.Errorf("Packet too short to contain length prefix")
}
// Extract the length prefix
packetLength := binary.BigEndian.Uint16(packet[:2])
if len(packet[2:]) < int(packetLength) {
return 0, nil, fmt.Errorf("Packet data is shorter than indicated length")
}
ciphertext := packet[2 : 2+packetLength]
// Decrypt the data
plaintext, err := ns.CipherState.Decrypt(nil, nil, ciphertext)
if err != nil {
return 0, nil, err
}
return len(plaintext), plaintext, nil
}
func (c *NoiseSession) readPacketLocked(data []byte) (int, error) {
log.WithField("data_length", len(data)).Debug("Starting readPacketLocked")

View File

@ -1,7 +1,6 @@
package noise
import (
"bytes"
"fmt"
"net"
"sync"
@ -19,19 +18,14 @@ import (
type NoiseSession struct {
router_info.RouterInfo
*noise.CipherState
sync.Mutex
*sync.Cond
*NoiseTransport // The parent transport, which "Dialed" the connection to the peer whith whom we established the session
RecvQueue *cb.Queue
SendQueue *cb.Queue
SendKey noise.DHKey
RecvKey noise.DHKey
HandKey noise.DHKey
VerifyCallback VerifyCallbackFunc
handshakeBuffer bytes.Buffer
activeCall int32
handshakeComplete bool
Conn net.Conn
*NoiseTransport // The parent transport, which "Dialed" the connection to the peer whith whom we established the session
*HandshakeState
RecvQueue *cb.Queue
SendQueue *cb.Queue
VerifyCallback VerifyCallbackFunc
activeCall int32
Conn net.Conn
}
// RemoteAddr implements net.Conn
@ -116,22 +110,13 @@ func NewNoiseTransportSession(ri router_info.RouterInfo) (transport.TransportSes
log.WithError(err).Error("Failed to dial address")
return nil, err
}
/*
return &NoiseSession{
SendQueue: cb.New(1024),
RecvQueue: cb.New(1024),
RouterInfo: ri,
Conn: socket,
}, nil
*/
session := &NoiseSession{
SendQueue: cb.New(1024),
RecvQueue: cb.New(1024),
RouterInfo: ri,
Conn: socket,
}
log.WithField("local_addr", socket.LocalAddr().String()).Info("NoiseTransportSession created successfully")
log.WithField("local_addr", socket.LocalAddr().String()).Debug("NoiseTransportSession created successfully")
return session, nil
}
log.Error("Failed to create NoiseTransportSession, all addresses failed")

View File

@ -13,7 +13,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/flynn/noise"
"github.com/go-i2p/go-i2p/lib/common/data"
"github.com/go-i2p/go-i2p/lib/common/router_identity"
"github.com/go-i2p/go-i2p/lib/common/router_info"
@ -23,7 +22,6 @@ import (
type NoiseTransport struct {
sync.Mutex
router_identity.RouterIdentity
*noise.CipherState
Listener net.Listener
peerConnections map[data.Hash]transport.TransportSession
}
@ -50,7 +48,7 @@ func (noopt *NoiseTransport) Accept() (net.Conn, error) {
if err != nil {
log.WithError(err).Error("NoiseTransport: Failed to accept connection")
} else {
log.WithField("remote_addr", conn.RemoteAddr().String()).Info("NoiseTransport: Accepted new connection")
log.WithField("remote_addr", conn.RemoteAddr().String()).Debug("NoiseTransport: Accepted new connection")
}
return conn, err
}
@ -156,7 +154,7 @@ func (noopt *NoiseTransport) Close() error {
// NewNoiseTransport create a NoiseTransport using a supplied net.Listener
func NewNoiseTransport(netSocket net.Listener) *NoiseTransport {
log.WithField("listener_addr", netSocket.Addr().String()).Info("Creating new NoiseTransport")
log.WithField("listener_addr", netSocket.Addr().String()).Debug("Creating new NoiseTransport")
return &NoiseTransport{
peerConnections: make(map[data.Hash]transport.TransportSession),
Listener: netSocket,
@ -174,6 +172,6 @@ func NewNoiseTransportSocket() (*NoiseTransport, error) {
}
// return NewNoiseTransport(netSocket), nil
_transport := NewNoiseTransport(netSocket)
log.WithField("addr", netSocket.Addr().String()).Info("Created new NoiseTransportSocket")
log.WithField("addr", netSocket.Addr().String()).Debug("Created new NoiseTransportSocket")
return _transport, nil
}

View File

@ -98,26 +98,6 @@ func (c *NoiseSession) encryptPacket(data []byte) (int, []byte, error) {
//c.freeBlock(packet)
}
func (ns *NoiseSession) encryptPacketDeux(plaintext []byte) (int, []byte, error) {
if ns.CipherState == nil {
return 0, nil, fmt.Errorf("CipherState is nil")
}
// Encrypt the data
ciphertext, err := ns.CipherState.Encrypt(nil, nil, plaintext)
if err != nil {
log.Fatalf("unimplemented\nerror:%v\n", err)
}
// Prepend the length of the ciphertext as a 2-byte big-endian value
packetLength := uint16(len(ciphertext))
packet := make([]byte, 2+len(ciphertext))
binary.BigEndian.PutUint16(packet[:2], packetLength)
copy(packet[2:], ciphertext)
return len(packet), packet, nil
}
func (c *NoiseSession) writePacketLocked(data []byte) (int, error) {
log.WithField("data_length", len(data)).Debug("NoiseSession: Starting writePacketLocked")

View File

@ -1,5 +1,101 @@
package ntcp
// Session implements TransportSession
// An established transport session
type Session struct{}
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/go-i2p/go-i2p/lib/transport/noise"
)
/*
Summary of what needs to be done:
NTCP and SSU2 are both transport protocols based on noise, with additional features designed to prevent p2p traffic from being blocked by firewalls.
These modifications affect how the Noise handshake takes place, in particular:
- Ephemeral keys are transmitted **obfuscated** by encrypting them with the peer's known static public key.
these modifications are simple enough, but for our purposes we also want to be able to re-use as much code as possible.
So, what we need to do is devise a means of adding these modifications to the existing NoiseSession implementation.
We could do this in any number of ways, we could:
1. Implement a custom struct that embeds a NoiseSession and overrides the Compose*HandshakeMessage functions
2. Modify the NoiseSession handshake functions to allow passing an obfuscation and/or padding function as a parameter
3. Modify the NoiseSession implementation to allow replacing the Compose*HandshakeMessage functions with custom ones
4. Refactor the NoiseSession implementation to break Compose*HandshakeMessage out into a separate interface, and implement that interface in a custom struct
Ideally, we're already set up to do #1, but we'll see how it goes.
Now is the right time to make changes if we need to, go-i2p is the only consumer of go-i2p right now, we can make our lives as easy as we want to.
*/
// NTCP2Session extends the base noise.NoiseSession with NTCP2-specific functionality
type NTCP2Session struct {
*noise.NoiseSession
paddingStrategy PaddingStrategy
}
// NewNTCP2Session creates a new NTCP2 session using the existing noise implementation
func NewNTCP2Session(noiseConfig router_info.RouterInfo) (*NTCP2Session, error) {
baseNoiseSession, err := noise.NewNoiseTransportSession(noiseConfig)
if err != nil {
return nil, err
}
return &NTCP2Session{
NoiseSession: baseNoiseSession.(*noise.NoiseSession),
}, nil
}
type PaddingStrategy interface {
AddPadding(message []byte) []byte
RemovePadding(message []byte) []byte
}
// PeerStaticKey is equal to the NTCP2 peer's static public key, found in their router info
func (s *NTCP2Session) peerStaticKey() ([32]byte, error) {
for _, addr := range s.RouterInfo.RouterAddresses() {
transportStyle, err := addr.TransportStyle().Data()
if err != nil {
continue
}
if transportStyle == NTCP_PROTOCOL_NAME {
return addr.StaticKey()
}
}
return [32]byte{}, fmt.Errorf("Remote static key error")
}
// ObfuscateEphemeral implements NTCP2's key obfuscation using AES-256-CBC
func (s *NTCP2Session) ObfuscateEphemeral(key []byte) ([]byte, error) {
static, err := s.peerStaticKey()
if err != nil {
return nil, err
}
block, err := aes.NewCipher(static[:])
if err != nil {
return nil, err
}
obfuscated := make([]byte, len(key))
iv := make([]byte, aes.BlockSize)
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(obfuscated, key)
return obfuscated, nil
}
// DeobfuscateEphemeral reverses the key obfuscation
func (s *NTCP2Session) DeobfuscateEphemeral(obfuscated []byte) ([]byte, error) {
static, err := s.peerStaticKey()
if err != nil {
return nil, err
}
block, err := aes.NewCipher(static[:])
if err != nil {
return nil, err
}
key := make([]byte, len(obfuscated))
iv := make([]byte, aes.BlockSize)
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(key, obfuscated)
return key, nil
}

View File

@ -4,11 +4,85 @@ package ntcp
* https://geti2p.net/spec/ntcp2
**/
import (
"fmt"
"net"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/go-i2p/go-i2p/lib/transport"
"github.com/go-i2p/go-i2p/lib/transport/noise"
)
const (
NTCP_PROTOCOL_VERSION = 2
NTCP_PROTOCOL_NAME = "NTCP2"
NTCP_MESSAGE_MAX_SIZE = 65537
)
// Transport is an ntcp transport implementing transport.Transport interface
type Transport struct{}
var exampleNTCPTransport transport.Transport = &Transport{}
// Transport is an ntcp2 transport implementing transport.Transport interface
type Transport struct {
*noise.NoiseTransport
}
func (t *Transport) Name() string {
return NTCP_PROTOCOL_NAME
}
func (t *Transport) Compatible(routerInfo router_info.RouterInfo) bool {
// Check if the router info contains NTCP2 address and capabilities
addresses := routerInfo.RouterAddresses()
for _, addr := range addresses {
transportStyle, err := addr.TransportStyle().Data()
if err != nil {
continue
}
if transportStyle == NTCP_PROTOCOL_NAME {
return true
}
}
return false
}
func (t *Transport) GetSession(routerInfo router_info.RouterInfo) (transport.TransportSession, error) {
// Create new NTCP2 session
session, err := NewNTCP2Session(routerInfo)
if err != nil {
return nil, err
}
// Perform handshake
if err := session.Handshake(routerInfo); err != nil {
return nil, err
}
return session, nil
}
func (t *Transport) Accept() (net.Conn, error) {
conn, err := t.NoiseTransport.Accept()
if err != nil {
return nil, err
}
// check if remote router address contains a compatible transport
// first get the RemoteAddr
remoteAddr := conn.LocalAddr()
// then check if it's a router address
routerAddr, ok := remoteAddr.(*router_info.RouterInfo)
if !ok {
return nil, fmt.Errorf("remote address is not a router address")
}
// then check if it's compatible
if !t.Compatible(*routerAddr) {
return nil, fmt.Errorf("remote router address is not compatible with NTCP2")
}
// Wrap connection with NTCP2 session
session, err := NewNTCP2Session(remoteAddr.(router_info.RouterInfo)) // nil for incoming connections
if err != nil {
conn.Close()
return nil, err
}
return session, nil
}

View File

@ -1,4 +1,4 @@
package ntcp
package obfs
import (
"fmt"

View File

@ -1 +1,10 @@
package ssu
/*
Summary of what needs to be done:
In addition to being a modified Noise protocol implementation,
SSU2 also includes peer-testing features and QUIC-inspired features for resuming interrupted sessions.
If we've done our jobs correctly when we get to this point, we will be implementing a net.Conn interface
that can do the peer-testing and session management stuff, and we will **layer** it with our Noise protocol
implementation and the SSU2 modifications.
*/

View File

@ -1 +1,17 @@
package ssu
/*
Summary of what needs to be done:
NTCP and SSU2 are both transport protocols based on noise, with additional features designed to prevent p2p traffic from being blocked by firewalls.
These modifications affect how the Noise handshake takes place, in particular:
- Ephemeral keys are transmitted **obfuscated** by encrypting them with the peer's known static public key.
these modifications are simple enough, but for our purposes we also want to be able to re-use as much code as possible.
So, what we need to do is devise a means of adding these modifications to the existing NoiseSession implementation.
We could do this in any number of ways, we could:
1. Implement a custom struct that embeds a NoiseSession and overrides the Compose*HandshakeMessage functions
2. Modify the NoiseSession handshake functions to allow passing an obfuscation and/or padding function as a parameter
3. Modify the NoiseSession implementation to allow replacing the Compose*HandshakeMessage functions with custom ones
4. Refactor the NoiseSession implementation to break Compose*HandshakeMessage out into a separate interface, and implement that interface in a custom struct
Ideally, we're already set up to do #1, but we'll see how it goes.
Now is the right time to make changes if we need to, go-i2p is the only consumer of go-i2p right now, we can make our lives as easy as we want to.
*/

View File

@ -22,6 +22,8 @@ type TransportSession interface {
// close the session cleanly
// returns any errors that happen while closing the session
Close() error
// create a handshake message for the session
// CreateHandshakeMessage() (i2np.I2NPMessage, error)
}
type Transport interface {

View File

@ -0,0 +1,249 @@
AD,EU
AE,AS
AF,AS
AG,NA
AI,NA
AL,EU
AM,AS
AO,AF
AQ,AN
AR,SA
AS,OC
AT,EU
AU,OC
AW,NA
AX,EU
AZ,AS
BA,EU
BB,NA
BD,AS
BE,EU
BF,AF
BG,EU
BH,AS
BI,AF
BJ,AF
BL,NA
BM,NA
BN,AS
BO,SA
BQ,NA
BR,SA
BS,NA
BT,AS
BV,AN
BW,AF
BY,EU
BZ,NA
CA,NA
CC,AS
CD,AF
CF,AF
CG,AF
CH,EU
CI,AF
CK,OC
CL,SA
CM,AF
CN,AS
CO,SA
CR,NA
CU,NA
CV,AF
CW,NA
CX,AS
CY,AS
CZ,EU
DE,EU
DJ,AF
DK,EU
DM,NA
DO,NA
DZ,AF
EC,SA
EE,EU
EG,AF
EH,AF
ER,AF
ES,EU
ET,AF
FI,EU
FJ,OC
FK,SA
FM,OC
FO,EU
FR,EU
GA,AF
GB,EU
GD,NA
GE,AS
GF,SA
GG,EU
GH,AF
GI,EU
GL,NA
GM,AF
GN,AF
GP,NA
GQ,AF
GR,EU
GS,AN
GT,NA
GU,OC
GW,AF
GY,SA
HK,AS
HM,AN
HN,NA
HR,EU
HT,NA
HU,EU
ID,AS
IE,EU
IL,AS
IM,EU
IN,AS
IO,AS
IQ,AS
IR,AS
IS,EU
IT,EU
JE,EU
JM,NA
JO,AS
JP,AS
KE,AF
KG,AS
KH,AS
KI,OC
KM,AF
KN,NA
KP,AS
KR,AS
KW,AS
KY,NA
KZ,AS
LA,AS
LB,AS
LC,NA
LI,EU
LK,AS
LR,AF
LS,AF
LT,EU
LU,EU
LV,EU
LY,AF
MA,AF
MC,EU
MD,EU
ME,EU
MF,NA
MG,AF
MH,OC
MK,EU
ML,AF
MM,AS
MN,AS
MO,AS
MP,OC
MQ,NA
MR,AF
MS,NA
MT,EU
MU,AF
MV,AS
MW,AF
MX,NA
MY,AS
MZ,AF
NA,AF
NC,OC
NE,AF
NF,OC
NG,AF
NI,NA
NL,EU
NO,EU
NP,AS
NR,OC
NU,OC
NZ,OC
OM,AS
PA,NA
PE,SA
PF,OC
PG,OC
PH,AS
PK,AS
PL,EU
PM,NA
PN,OC
PR,NA
PS,AS
PT,EU
PW,OC
PY,SA
QA,AS
RE,AF
RO,EU
RS,EU
RU,EU
RW,AF
SA,AS
SB,OC
SC,AF
SD,AF
SE,EU
SG,AS
SH,AF
SI,EU
SJ,EU
SK,EU
SL,AF
SM,EU
SN,AF
SO,AF
SR,SA
SS,AF
ST,AF
SV,NA
SX,NA
SY,AS
SZ,AF
TC,NA
TD,AF
TF,AN
TG,AF
TH,AS
TJ,AS
TK,OC
TL,AS
TM,AS
TN,AF
TO,OC
TR,AS
TT,NA
TV,OC
TW,AS
TZ,AF
UA,EU
UG,AF
UM,OC
US,NA
UY,SA
UZ,AS
VA,EU
VC,NA
VE,SA
VG,NA
VI,NA
VN,AS
VU,OC
WF,OC
WS,OC
YE,AS
YT,AF
ZA,AF
ZM,AF
ZW,AF

128
lib/util/time/sntp/doc.md Normal file
View File

@ -0,0 +1,128 @@
# sntp
--
import "github.com/go-i2p/go-i2p/lib/util/sntp"
## Usage
```go
import "github.com/go-i2p/go-i2p/lib/util/sntp"
```
## Types
### type RouterTimestamper
```go
type RouterTimestamper struct {
servers []string
priorityServers [][]string
listeners []UpdateListener
queryFrequency time.Duration
concurringServers int
consecutiveFails int
disabled bool
initialized bool
wellSynced bool
isRunning bool
mutex sync.Mutex
zones *Zones
stopChan chan struct{}
waitGroup sync.WaitGroup
ntpClient NTPClient
}
```
RouterTimestamper is responsible for querying NTP servers and managing time synchronization.
#### func NewRouterTimestamper
```go
func NewRouterTimestamper(client NTPClient) *RouterTimestamper
```
NewRouterTimestamper creates a new RouterTimestamper instance.
#### func (*RouterTimestamper) Start
```go
func (rt *RouterTimestamper) Start()
```
Start begins the time synchronization process.
#### func (*RouterTimestamper) Stop
```go
func (rt *RouterTimestamper) Stop()
```
Stop halts the time synchronization process.
#### func (*RouterTimestamper) AddListener
```go
func (rt *RouterTimestamper) AddListener(listener UpdateListener)
```
AddListener adds a new listener for time updates.
#### func (*RouterTimestamper) RemoveListener
```go
func (rt *RouterTimestamper) RemoveListener(listener UpdateListener)
```
RemoveListener removes a listener from receiving time updates.
#### func (*RouterTimestamper) WaitForInitialization
```go
func (rt *RouterTimestamper) WaitForInitialization()
```
WaitForInitialization blocks until the RouterTimestamper is initialized or a timeout occurs.
#### func (*RouterTimestamper) TimestampNow
```go
func (rt *RouterTimestamper) TimestampNow()
```
TimestampNow triggers an immediate time synchronization.
### type UpdateListener
```go
type UpdateListener interface {
SetNow(now time.Time, stratum uint8)
}
```
UpdateListener is an interface that listeners must implement to receive time updates.
### type Zones
```go
type Zones struct {
countryToZone map[string]string
continentToZone map[string]string
}
```
Zones manages mappings between country codes, continent codes, and NTP zones.
#### func NewZones
```go
func NewZones() *Zones
```
NewZones creates a new Zones instance and initializes it with data.
#### func (*Zones) GetZone
```go
func (z *Zones) GetZone(countryCode string) string
```
GetZone returns the NTP zone for a given country code.

View File

@ -0,0 +1,395 @@
package sntp
import (
"fmt"
"math/rand"
"net"
"strings"
"sync"
"time"
"github.com/beevik/ntp"
)
type NTPClient interface {
QueryWithOptions(host string, options ntp.QueryOptions) (*ntp.Response, error)
}
type DefaultNTPClient struct{}
func (c *DefaultNTPClient) QueryWithOptions(host string, options ntp.QueryOptions) (*ntp.Response, error) {
return ntp.QueryWithOptions(host, options)
}
type RouterTimestamper struct {
servers []string
priorityServers [][]string
listeners []UpdateListener
queryFrequency time.Duration
concurringServers int
consecutiveFails int
disabled bool
initialized bool
wellSynced bool
isRunning bool
mutex sync.Mutex
zones *Zones
stopChan chan struct{}
waitGroup sync.WaitGroup
ntpClient NTPClient
}
const (
minQueryFrequency = 5 * time.Minute
defaultQueryFrequency = 11 * time.Minute
defaultServerList = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org"
defaultDisabled = false
defaultConcurring = 3
maxConsecutiveFails = 10
defaultTimeout = 10 * time.Second
shortTimeout = 5 * time.Second
maxWaitInitialization = 45 * time.Second
maxVariance = 10 * time.Second
)
func NewRouterTimestamper(client NTPClient) *RouterTimestamper {
rt := &RouterTimestamper{
listeners: []UpdateListener{},
disabled: defaultDisabled,
queryFrequency: defaultQueryFrequency,
concurringServers: defaultConcurring,
zones: NewZones(),
stopChan: make(chan struct{}),
ntpClient: client,
}
rt.updateConfig()
return rt
}
func (rt *RouterTimestamper) Start() {
if rt.disabled || rt.initialized {
return
}
rt.isRunning = true
rt.waitGroup.Add(1)
go rt.run()
}
func (rt *RouterTimestamper) Stop() {
if rt.isRunning {
rt.isRunning = false
close(rt.stopChan)
rt.waitGroup.Wait()
}
}
func (rt *RouterTimestamper) AddListener(listener UpdateListener) {
rt.mutex.Lock()
defer rt.mutex.Unlock()
rt.listeners = append(rt.listeners, listener)
}
func (rt *RouterTimestamper) RemoveListener(listener UpdateListener) {
rt.mutex.Lock()
defer rt.mutex.Unlock()
for i, l := range rt.listeners {
if l == listener {
rt.listeners = append(rt.listeners[:i], rt.listeners[i+1:]...)
break
}
}
}
func (rt *RouterTimestamper) WaitForInitialization() {
start := time.Now()
for {
rt.mutex.Lock()
initialized := rt.initialized
rt.mutex.Unlock()
if initialized {
return
}
if time.Since(start) > maxWaitInitialization {
return
}
time.Sleep(100 * time.Millisecond)
}
}
func (rt *RouterTimestamper) TimestampNow() {
if rt.initialized && rt.isRunning && !rt.disabled {
go rt.runOnce()
}
}
func (rt *RouterTimestamper) secureRandBool(probability float64) bool {
return rand.Float64() < probability
}
func (rt *RouterTimestamper) performTimeQuery() bool {
rt.updateConfig()
preferIPv6 := checkIPv6Connectivity()
if rt.disabled {
return false
}
lastFailed := true
if rt.priorityServers != nil {
for _, servers := range rt.priorityServers {
lastFailed = !rt.queryTime(servers, shortTimeout, preferIPv6)
if !lastFailed {
break
}
}
}
if rt.priorityServers == nil || lastFailed {
prefIPv6 := preferIPv6 && rt.secureRandBool(0.75)
lastFailed = !rt.queryTime(rt.servers, defaultTimeout, prefIPv6)
}
rt.mutex.Lock()
if !rt.initialized {
rt.initialized = true
}
rt.mutex.Unlock()
return lastFailed
}
/*
func (rt *RouterTimestamper) run() {
defer rt.waitGroup.Done()
lastFailed := false
for rt.isRunning {
rt.updateConfig()
preferIPv6 := checkIPv6Connectivity()
if !rt.disabled {
if rt.priorityServers != nil {
for _, servers := range rt.priorityServers {
lastFailed = !rt.queryTime(servers, shortTimeout, preferIPv6)
if !lastFailed {
break
}
}
}
if rt.priorityServers == nil || lastFailed {
prefIPv6 := preferIPv6 && !lastFailed && rand.Intn(4) != 0
lastFailed = !rt.queryTime(rt.servers, defaultTimeout, prefIPv6)
}
}
rt.mutex.Lock()
if !rt.initialized {
rt.initialized = true
}
rt.mutex.Unlock()
var sleepTime time.Duration
if lastFailed {
rt.consecutiveFails++
if rt.consecutiveFails >= maxConsecutiveFails {
sleepTime = 30 * time.Minute
} else {
sleepTime = 30 * time.Second
}
} else {
rt.consecutiveFails = 0
randomDelay := time.Duration(rand.Int63n(int64(rt.queryFrequency / 2)))
sleepTime = rt.queryFrequency + randomDelay
if rt.wellSynced {
sleepTime *= 3
}
}
select {
case <-time.After(sleepTime):
case <-rt.stopChan:
return
}
}
}
*/
func (rt *RouterTimestamper) run() {
defer rt.waitGroup.Done()
for rt.isRunning {
lastFailed := rt.performTimeQuery()
var sleepTime time.Duration
if lastFailed {
rt.consecutiveFails++
if rt.consecutiveFails >= maxConsecutiveFails {
sleepTime = 30 * time.Minute
} else {
sleepTime = 30 * time.Second
}
} else {
rt.consecutiveFails = 0
randomDelay := time.Duration(rand.Int63n(int64(rt.queryFrequency / 2)))
sleepTime = rt.queryFrequency + randomDelay
if rt.wellSynced {
sleepTime *= 3
}
}
select {
case <-time.After(sleepTime):
case <-rt.stopChan:
return
}
}
}
/*
func (rt *RouterTimestamper) runOnce() {
lastFailed := false
rt.updateConfig()
preferIPv6 := checkIPv6Connectivity()
if !rt.disabled {
if rt.priorityServers != nil {
for _, servers := range rt.priorityServers {
lastFailed = !rt.queryTime(servers, shortTimeout, preferIPv6)
if !lastFailed {
break
}
}
}
if rt.priorityServers == nil || lastFailed {
prefIPv6 := preferIPv6 && !lastFailed && rand.Intn(4) != 0
lastFailed = !rt.queryTime(rt.servers, defaultTimeout, prefIPv6)
}
}
rt.mutex.Lock()
if !rt.initialized {
rt.initialized = true
}
rt.mutex.Unlock()
}
*/
func (rt *RouterTimestamper) runOnce() {
rt.performTimeQuery()
}
func (rt *RouterTimestamper) queryTime(servers []string, timeout time.Duration, preferIPv6 bool) bool {
found := make([]time.Duration, rt.concurringServers)
var expectedDelta time.Duration
rt.wellSynced = false
for i := 0; i < rt.concurringServers; i++ {
server := servers[rand.Intn(len(servers))]
options := ntp.QueryOptions{
Timeout: timeout,
// TTL: 5,
}
if preferIPv6 {
server = fmt.Sprintf("[%s]:123", server)
}
response, err := rt.ntpClient.QueryWithOptions(server, options)
if err != nil {
fmt.Printf("NTP query failed: %v\n", err)
return false
}
now := time.Now().Add(response.ClockOffset)
delta := now.Sub(time.Now())
found[i] = delta
if i == 0 {
if absDuration(delta) < maxVariance {
if absDuration(delta) < 500*time.Millisecond {
rt.wellSynced = true
}
break
} else {
expectedDelta = delta
}
} else {
if absDuration(delta-expectedDelta) > maxVariance {
// Variance too high, fail this attempt
return false
}
}
}
rt.stampTime(time.Now().Add(found[0]))
return true
}
func (rt *RouterTimestamper) stampTime(now time.Time) {
rt.mutex.Lock()
defer rt.mutex.Unlock()
for _, listener := range rt.listeners {
listener.SetNow(now, 0)
}
}
func (rt *RouterTimestamper) updateConfig() {
serverList := defaultServerList
rt.servers = strings.Split(serverList, ",")
for i, server := range rt.servers {
rt.servers[i] = strings.TrimSpace(server)
}
if rt.queryFrequency < minQueryFrequency {
rt.queryFrequency = minQueryFrequency
}
if rt.concurringServers < 1 {
rt.concurringServers = 1
} else if rt.concurringServers > 4 {
rt.concurringServers = 4
}
country := getLocalCountryCode()
if country != "" && country != "a1" && country != "a2" {
rt.priorityServers = [][]string{}
p1 := []string{
fmt.Sprintf("0.%s.pool.ntp.org", country),
fmt.Sprintf("1.%s.pool.ntp.org", country),
fmt.Sprintf("2.%s.pool.ntp.org", country),
}
rt.priorityServers = append(rt.priorityServers, p1)
zone := rt.zones.GetZone(country)
if zone != "" {
p2 := []string{
fmt.Sprintf("0.%s.pool.ntp.org", zone),
fmt.Sprintf("1.%s.pool.ntp.org", zone),
fmt.Sprintf("2.%s.pool.ntp.org", zone),
}
rt.priorityServers = append(rt.priorityServers, p2)
}
} else {
rt.priorityServers = nil
}
}
func checkIPv6Connectivity() bool {
addrs, err := net.InterfaceAddrs()
if err != nil {
return false
}
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To16() != nil && ipNet.IP.To4() == nil {
return true
}
}
}
return false
}
func absDuration(d time.Duration) time.Duration {
if d < 0 {
return -d
}
return d
}
func getLocalCountryCode() string {
return ""
}

View File

@ -0,0 +1,205 @@
package sntp
import (
"sync"
"testing"
"time"
"github.com/beevik/ntp"
)
type MockNTPClient struct {
ClockOffset time.Duration
Error error
}
func (c *MockNTPClient) QueryWithOptions(host string, options ntp.QueryOptions) (*ntp.Response, error) {
if c.Error != nil {
return nil, c.Error
}
return &ntp.Response{
ClockOffset: c.ClockOffset,
}, nil
}
type MockListener struct {
mu sync.Mutex
updates []time.Time
stratums []uint8
}
func (ml *MockListener) SetNow(now time.Time, stratum uint8) {
ml.mu.Lock()
defer ml.mu.Unlock()
ml.updates = append(ml.updates, now)
ml.stratums = append(ml.stratums, stratum)
}
func TestRouterTimestamperInitialization(t *testing.T) {
defaultClient := &DefaultNTPClient{}
timestamper := NewRouterTimestamper(defaultClient)
if timestamper == nil {
t.Fatal("Expected RouterTimestamper instance, got nil")
}
}
func TestAddAndRemoveListener(t *testing.T) {
defaultClient := &DefaultNTPClient{}
timestamper := NewRouterTimestamper(defaultClient)
listener := &MockListener{}
timestamper.AddListener(listener)
if len(timestamper.listeners) != 1 {
t.Errorf("Expected 1 listener, got %d", len(timestamper.listeners))
}
timestamper.RemoveListener(listener)
if len(timestamper.listeners) != 0 {
t.Errorf("Expected 0 listeners, got %d", len(timestamper.listeners))
}
}
func TestTimestampNow(t *testing.T) {
defaultClient := &DefaultNTPClient{}
timestamper := NewRouterTimestamper(defaultClient)
listener := &MockListener{}
timestamper.AddListener(listener)
// Mock Injection
mockNTPClient := &MockNTPClient{
ClockOffset: 1 * time.Second,
}
timestamper.ntpClient = mockNTPClient
timestamper.Start()
defer timestamper.Stop()
timestamper.WaitForInitialization()
// Trigger update
timestamper.TimestampNow()
time.Sleep(100 * time.Millisecond)
listener.mu.Lock()
defer listener.mu.Unlock()
if len(listener.updates) == 0 {
t.Error("Expected at least one time update, got none")
}
}
func TestTimestampNowWithRealNTP(t *testing.T) {
defaultClient := &DefaultNTPClient{}
timestamper := NewRouterTimestamper(defaultClient)
listener := &MockListener{}
timestamper.AddListener(listener)
timestamper.Start()
defer timestamper.Stop()
t.Log("Waiting for initialization...")
timestamper.WaitForInitialization()
t.Log("Initialization complete")
// Trigger an immediate time update
t.Log("Triggering time update...")
timestamper.TimestampNow()
timeout := time.After(30 * time.Second)
updateReceived := make(chan struct{})
go func() {
for {
listener.mu.Lock()
if len(listener.updates) > 0 {
listener.mu.Unlock()
updateReceived <- struct{}{}
return
}
listener.mu.Unlock()
time.Sleep(100 * time.Millisecond)
}
}()
select {
case <-updateReceived:
t.Log("Update received successfully")
case <-timeout:
t.Error("Timed out waiting for NTP update")
}
listener.mu.Lock()
defer listener.mu.Unlock()
if len(listener.updates) == 0 {
t.Error("Expected at least one time update, got none")
} else {
t.Logf("Received %d updates", len(listener.updates))
for i, update := range listener.updates {
t.Logf("Update %d: %v", i, update)
}
}
t.Logf("NTP Servers: %v", timestamper.servers)
t.Logf("Priority Servers: %v", timestamper.priorityServers)
}
func TestWaitForInitialization(t *testing.T) {
defaultClient := &DefaultNTPClient{}
timestamper := NewRouterTimestamper(defaultClient)
start := time.Now()
go func() {
time.Sleep(1 * time.Second)
timestamper.mutex.Lock()
timestamper.initialized = true
timestamper.mutex.Unlock()
}()
timestamper.WaitForInitialization()
elapsed := time.Since(start)
if elapsed < 1*time.Second {
t.Errorf("Expected to wait at least 1 second, waited %v", elapsed)
}
}
func TestQueryTime(t *testing.T) {
defaultClient := &DefaultNTPClient{}
timestamper := NewRouterTimestamper(defaultClient)
listener := &MockListener{}
timestamper.AddListener(listener)
// Mock injection
mockNTPClient := &MockNTPClient{
ClockOffset: 1 * time.Second,
}
timestamper.ntpClient = mockNTPClient
servers := []string{"pool.ntp.org"}
success := timestamper.queryTime(servers, 5*time.Second, false)
if !success {
t.Error("Expected queryTime to succeed")
}
// Ensure that the listener received an update
listener.mu.Lock()
defer listener.mu.Unlock()
if len(listener.updates) == 0 {
t.Error("Expected listener to receive time update")
}
}
func TestUpdateConfig(t *testing.T) {
defaultClient := &DefaultNTPClient{}
timestamper := NewRouterTimestamper(defaultClient)
// Modify the default configuration
timestamper.queryFrequency = 1 * time.Minute
timestamper.concurringServers = 5
timestamper.updateConfig()
if timestamper.queryFrequency < minQueryFrequency {
t.Errorf("Expected queryFrequency >= %v, got %v", minQueryFrequency, timestamper.queryFrequency)
}
if timestamper.concurringServers > 4 {
t.Errorf("Expected concurringServers <= 4, got %d", timestamper.concurringServers)
}
}

View File

@ -0,0 +1,8 @@
package sntp
import "time"
// UpdateListener is an interface that listeners must implement to receive time updates.
type UpdateListener interface {
SetNow(now time.Time, stratum uint8)
}

View File

@ -0,0 +1,57 @@
package sntp
import (
"fmt"
"time"
"github.com/beevik/ntp"
)
func (rt *RouterTimestamper) validateResponse(response *ntp.Response) bool {
// Check Leap Indicator
if response.Leap == ntp.LeapNotInSync {
fmt.Println("Invalid response: Server clock not synchronized (Leap Indicator)")
return false
}
// Check Stratum Level
if response.Stratum == 0 || response.Stratum > 15 {
fmt.Printf("Invalid response: Stratum level %d is out of valid range\n", response.Stratum)
return false
}
// Round-Trip Delay and Clock Offset Sanity Checks
if response.RTT < 0 || response.RTT > maxRTT {
fmt.Printf("Invalid response: Round-trip delay %v is out of bounds\n", response.RTT)
return false
}
if absDuration(response.ClockOffset) > maxClockOffset {
fmt.Printf("Invalid response: Clock offset %v is out of bounds\n", response.ClockOffset)
return false
}
// Non-zero Time
if response.Time.IsZero() {
fmt.Println("Invalid response: Received zero time")
return false
}
// Root Dispersion and Root Delay
if response.RootDispersion > maxRootDispersion {
fmt.Printf("Invalid response: Root dispersion %v is too high\n", response.RootDispersion)
return false
}
if response.RootDelay > maxRootDelay {
fmt.Printf("Invalid response: Root delay %v is too high\n", response.RootDelay)
return false
}
return true
}
const (
maxRTT = 2 * time.Second // Max acceptable round-trip time
maxClockOffset = 10 * time.Second // Max acceptable clock offset
maxRootDispersion = 1 * time.Second // Max acceptable root dispersion
maxRootDelay = 1 * time.Second // Maxi acceptable root delay
)

View File

@ -0,0 +1,49 @@
package sntp
import (
"testing"
"time"
"github.com/beevik/ntp"
)
func TestValidateResponse(t *testing.T) {
rt := &RouterTimestamper{}
// Valid response
validResponse := &ntp.Response{
Leap: ntp.LeapNoWarning,
Stratum: 2,
RTT: 50 * time.Millisecond,
ClockOffset: 100 * time.Millisecond,
Time: time.Now(),
RootDispersion: 500 * time.Millisecond,
RootDelay: 10 * time.Millisecond,
KissCode: "",
}
if !rt.validateResponse(validResponse) {
t.Error("Expected valid response to pass validation")
}
// Invalid Leap Indicator
invalidLeapResponse := *validResponse
invalidLeapResponse.Leap = ntp.LeapNotInSync
if rt.validateResponse(&invalidLeapResponse) {
t.Error("Expected response with invalid leap indicator to fail validation")
}
// Invalid Stratum
invalidStratumResponse := *validResponse
invalidStratumResponse.Stratum = 0
if rt.validateResponse(&invalidStratumResponse) {
t.Error("Expected response with invalid stratum to fail validation")
}
// High Root Dispersion
highRootDispersionResponse := *validResponse
highRootDispersionResponse.RootDispersion = 2 * time.Second
if rt.validateResponse(&highRootDispersionResponse) {
t.Error("Expected response with high root dispersion to fail validation")
}
}

View File

@ -0,0 +1,90 @@
package sntp
import (
"bufio"
"embed"
"io"
"log"
"strings"
)
//go:embed continents.txt
var continentsFS embed.FS
type Zones struct {
countryToZone map[string]string
continentToZone map[string]string
}
func NewZones() *Zones {
z := &Zones{
countryToZone: make(map[string]string),
continentToZone: make(map[string]string),
}
z.initialize()
return z
}
func (z *Zones) GetZone(countryCode string) string {
countryCode = strings.ToLower(countryCode)
if zone, ok := z.countryToZone[countryCode]; ok {
return zone
}
return ""
}
func (z *Zones) initialize() {
zones := []string{
"AF", "africa",
"AN", "antarctica", // Who is living here?
"AS", "asia",
"EU", "europe",
"NA", "north-america",
"OC", "oceania",
"SA", "south-america",
}
for i := 0; i < len(zones); i += 2 {
z.continentToZone[zones[i]] = zones[i+1]
}
z.readContinentFile()
}
func (z *Zones) readContinentFile() {
file, err := continentsFS.Open("continents.txt")
if err != nil {
log.Printf("Error opening continents.txt: %v\n", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
log.Printf("Error reading continents.txt: %v\n", err)
break
}
if err == io.EOF && line == "" {
break
}
line = strings.TrimSpace(line)
if len(line) == 0 || strings.HasPrefix(line, "#") {
continue
}
parts := strings.Split(line, ",")
if len(parts) < 2 {
continue
}
countryCode := strings.ToLower(strings.TrimSpace(parts[0]))
continentCode := strings.ToUpper(strings.TrimSpace(parts[1]))
if zone, ok := z.continentToZone[continentCode]; ok {
z.countryToZone[countryCode] = zone
}
}
}

151
main.go
View File

@ -1,37 +1,162 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/go-i2p/go-i2p/lib/config"
"github.com/go-i2p/go-i2p/lib/router"
"github.com/go-i2p/go-i2p/lib/util/logger"
"github.com/go-i2p/go-i2p/lib/util/signals"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
var log = logger.GetGoI2PLogger()
var (
routerInstance *router.Router
log = logger.GetGoI2PLogger()
)
func main() {
netDbPath := flag.String("netDb", config.DefaultNetDbConfig.Path, "Path to the netDb")
flag.Parse()
config.RouterConfigProperties.NetDb.Path = *netDbPath
var RootCmd = &cobra.Command{
Use: "go-i2p",
Short: "I2P Router implementation in Go",
Run: func(cmd *cobra.Command, args []string) {
runRouter()
},
}
func init() {
cobra.OnInitialize(config.InitConfig)
// Global flags
RootCmd.PersistentFlags().StringVar(&config.CfgFile, "config", "", "config file (default is $HOME/.go-i2p/config.yaml)")
// Router configuration flags
RootCmd.PersistentFlags().String("base-dir", config.DefaultRouterConfig().BaseDir, "Base directory for I2P router")
RootCmd.PersistentFlags().String("working-dir", config.DefaultRouterConfig().WorkingDir, "Working directory for I2P router")
// NetDb flags
RootCmd.PersistentFlags().String("netdb.path", config.DefaultNetDbConfig.Path, "Path to the netDb")
// Bootstrap flags
RootCmd.PersistentFlags().Int("bootstrap.low-peer-threshold", config.DefaultBootstrapConfig.LowPeerThreshold,
"Minimum number of peers before reseeding")
// Bind flags to viper
viper.BindPFlag("base_dir", RootCmd.PersistentFlags().Lookup("base-dir"))
viper.BindPFlag("working_dir", RootCmd.PersistentFlags().Lookup("working-dir"))
viper.BindPFlag("netdb.path", RootCmd.PersistentFlags().Lookup("netdb.path"))
viper.BindPFlag("bootstrap.low_peer_threshold", RootCmd.PersistentFlags().Lookup("bootstrap.low-peer-threshold"))
}
// configCmd shows current configuration
var configCmd = &cobra.Command{
Use: "config",
Short: "Show current configuration",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Configuration file: %s\n", viper.ConfigFileUsed())
fmt.Printf("\nRouter Configuration:")
fmt.Printf(" Base Directory: %s", config.RouterConfigProperties.BaseDir)
fmt.Printf(" Working Directory: %s", config.RouterConfigProperties.WorkingDir)
fmt.Printf("\nNetDb Configuration:")
fmt.Printf(" Path: %s", config.RouterConfigProperties.NetDb.Path)
fmt.Printf("\nBootstrap Configuration:")
fmt.Printf(" Low Peer Threshold: %d", config.RouterConfigProperties.Bootstrap.LowPeerThreshold)
fmt.Printf(" Reseed Servers:")
for _, server := range config.RouterConfigProperties.Bootstrap.ReseedServers {
fmt.Printf(" - URL: %s", server.Url)
fmt.Printf(" SU3 Fingerprint: %s", server.SU3Fingerprint)
}
},
}
func debugPrintConfig() {
currentConfig := struct {
BaseDir string `yaml:"base_dir"`
WorkingDir string `yaml:"working_dir"`
NetDB config.NetDbConfig `yaml:"netdb"`
Bootstrap config.BootstrapConfig `yaml:"bootstrap"`
}{
BaseDir: config.RouterConfigProperties.BaseDir,
WorkingDir: config.RouterConfigProperties.WorkingDir,
NetDB: *config.RouterConfigProperties.NetDb,
Bootstrap: *config.RouterConfigProperties.Bootstrap,
}
yamlData, err := yaml.Marshal(currentConfig)
if err != nil {
log.Errorf("Error marshaling config for debug: %s", err)
return
}
log.Debugf("Current configuration:\n%s", string(yamlData))
}
func runRouter() {
go signals.Handle()
log.Debug("parsing i2p router configuration")
log.Debug("using netDb in:", config.RouterConfigProperties.NetDb.Path)
log.Debug("starting up i2p router")
r, err := router.CreateRouter()
var err error
routerInstance, err = router.CreateRouter(config.RouterConfigProperties)
if err == nil {
signals.RegisterReloadHandler(func() {
// TODO: reload config
if err := viper.ReadInConfig(); err != nil {
log.Errorf("failed to reload config: %s", err)
return
}
config.UpdateRouterConfig()
})
signals.RegisterInterruptHandler(func() {
// TODO: graceful shutdown
r.Stop()
if routerInstance != nil {
routerInstance.Stop()
}
})
r.Start()
r.Wait()
r.Close()
routerInstance.Start()
routerInstance.Wait()
routerInstance.Close()
} else {
log.Errorf("failed to create i2p router: %s", err)
}
}
func main() {
RootCmd.AddCommand(configCmd)
if err := RootCmd.Execute(); err != nil {
log.Error(err)
debugPrintConfig()
os.Exit(1)
}
/*
netDbPath := flag.String("netDb", config.DefaultNetDbConfig.Path, "Path to the netDb")
flag.Parse()
config.RouterConfigProperties.NetDb.Path = *netDbPath
go signals.Handle()
log.Debug("parsing i2p router configuration")
log.Debug("using netDb in:", config.RouterConfigProperties.NetDb.Path)
log.Debug("starting up i2p router")
r, err := router.CreateRouter()
if err == nil {
signals.RegisterReloadHandler(func() {
// TODO: reload config
})
signals.RegisterInterruptHandler(func() {
// TODO: graceful shutdown
r.Stop()
})
r.Start()
r.Wait()
r.Close()
} else {
log.Errorf("failed to create i2p router: %s", err)
}
*/
}