Compare commits
58 Commits
noise-expe
...
old-router
Author | SHA1 | Date | |
---|---|---|---|
7709efff2b | |||
015c4b23e2 | |||
e29c3c7abb | |||
6f6291a9f6 | |||
767864d457 | |||
0a98236d85 | |||
c1fa63f6ec | |||
a75c275b4c | |||
d40b3e0cd3 | |||
2ee2d77d7c | |||
dc9b969bcf | |||
575e877115 | |||
271cf56ded | |||
a29fa0bc03 | |||
63c48dd3b7 | |||
8bec47efd2 | |||
69a50e2035 | |||
8319444890 | |||
b378661e0e | |||
f4086e5f68 | |||
877fc707c4 | |||
98d05e27c8 | |||
4020db8a19 | |||
67a02f5d69 | |||
ca1280231c | |||
02b309df43 | |||
a5b3c3f194 | |||
0aa7a5554b | |||
266a1b71d6 | |||
d32f2e78ab | |||
9e806bc32e | |||
c52112a36f | |||
db0fd9f7e9 | |||
3ab258cde6 | |||
20b9bbd8e4 | |||
1fa520613c | |||
fb99b98a7e | |||
d6b8cd9d4d | |||
92e4656774 | |||
5f2bfb8d9d | |||
9494c226a6 | |||
344edc6d41 | |||
9eea99b489 | |||
c984f94b90 | |||
a17f0208dd | |||
487815f8f1 | |||
24e0baa879 | |||
a5d2f0de8c | |||
423f616d53 | |||
c65048f3c4 | |||
39b683dac8 | |||
46883f6457 | |||
4be55062fc | |||
12a3fd4623 | |||
e468520906 | |||
ade80e577c | |||
f45d301868 | |||
8fa355f067 |
18
PASTA.md
18
PASTA.md
@ -1,18 +0,0 @@
|
||||
At long last... something useful
|
||||
================================
|
||||
|
||||
It's been 2 years of me mostly not having time to work on go-i2p itself since my last update.
|
||||
However, after much waiting, this library is actually **useful** for something.
|
||||
It is now being used in the `reseed-tools` application to examine RouterInfos prior to including them in reseed bundles.
|
||||
Routers that self-report as unreachable or congested will be excluded from future reseed bundles.
|
||||
Additionally, routers that self-report an old version will be excluded from reseed bundles.
|
||||
This should help new users build better connections faster with the existing, working router implementations.
|
||||
|
||||
This is not a working release of a go-i2p router
|
||||
------------------------------------------------
|
||||
|
||||
It is a numbered version of the go-i2p library, which is pre-release, expressly for use in the `reseed-tools` application.
|
||||
The common library works, and so do some of the cryptographic primitives, however the API is unstable and the software itself is certain to have serious bugs outside of a few well-tested areas.
|
||||
If you're using it for something other than parsing and analyzing RouterInfos and LeaseSets, you'll probably encounter bugs.
|
||||
Please report them to the https://github.com/go-i2p/go-i2p
|
||||
Use any part of it at your own risk.
|
@ -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
41
ROADMAP.md
Normal 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.
|
@ -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 \
|
||||
|
@ -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
|
@ -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
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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
|
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -1,2 +1,2 @@
|
||||
test-mapping-values-order:
|
||||
go test -v ./lib/common/data -run TestMappingOrderSortsValuesThenKeys
|
||||
$(GO) test -v ./lib/common/data -run TestMappingOrderSortsValuesThenKeys
|
@ -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
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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
28
go.mod
@ -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
71
go.sum
@ -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=
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
65
lib/common/data/hash_test.go
Normal file
65
lib/common/data/hash_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
|
112
lib/common/router_info/router_info2_test.go
Normal file
112
lib/common/router_info/router_info2_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
@ -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
113
lib/config/config.go
Normal 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,
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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[:])
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
300
lib/transport/noise/doc.md
Normal 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)
|
||||
```
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package ntcp
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
249
lib/util/time/sntp/continents.txt
Normal file
249
lib/util/time/sntp/continents.txt
Normal 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
128
lib/util/time/sntp/doc.md
Normal 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.
|
395
lib/util/time/sntp/router_timestamper.go
Normal file
395
lib/util/time/sntp/router_timestamper.go
Normal 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 ""
|
||||
}
|
205
lib/util/time/sntp/router_timestamper_test.go
Normal file
205
lib/util/time/sntp/router_timestamper_test.go
Normal 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)
|
||||
}
|
||||
}
|
8
lib/util/time/sntp/update_listener.go
Normal file
8
lib/util/time/sntp/update_listener.go
Normal 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)
|
||||
}
|
57
lib/util/time/sntp/verification.go
Normal file
57
lib/util/time/sntp/verification.go
Normal 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
|
||||
)
|
49
lib/util/time/sntp/verification_test.go
Normal file
49
lib/util/time/sntp/verification_test.go
Normal 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")
|
||||
}
|
||||
}
|
90
lib/util/time/sntp/zones.go
Normal file
90
lib/util/time/sntp/zones.go
Normal 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
151
main.go
@ -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)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
Reference in New Issue
Block a user