More tests for torutil and test explanation
This commit is contained in:
parent
00909b144c
commit
f33268f084
15
README.md
15
README.md
|
@ -11,6 +11,11 @@ Features:
|
|||
* Supports statically compiled Tor to embed Tor into the binary
|
||||
* Supports both V2 and V3 onion services
|
||||
|
||||
See info below, the [API docs](http://godoc.org/github.com/cretz/bine), and the [examples](examples). The project is
|
||||
MIT licensed. The Tor docs/specs and https://github.com/yawning/bulb were great helps when building this.
|
||||
|
||||
## Example
|
||||
|
||||
It is really easy to create an onion service. For example, assuming `tor` is on the `PATH`, this bit of code will show
|
||||
a directory server of the current directory:
|
||||
|
||||
|
@ -72,5 +77,11 @@ t, err := tor.Start(nil, &tor.StartConf{ProcessCreator: embedded.NewCreator()})
|
|||
Tested on Windows, the original exe file is ~7MB. With Tor statically linked it comes to ~24MB, but Tor does not have to
|
||||
be distributed separately. Of course take notice of all licenses in accompanying projects.
|
||||
|
||||
Also take a look at the [API docs](http://godoc.org/github.com/cretz/bine) and the [examples](examples). The project is
|
||||
MIT licensed. The Tor docs/specs and https://github.com/yawning/bulb were great helps when building this.
|
||||
## Testing
|
||||
|
||||
To test, a simple `go test ./...` from the base of the repository will work (add in a `-v` in there to see the tests).
|
||||
The integration tests in `tests` however will be skipped. To execute those tests, `-tor` must be passed to the test.
|
||||
Also, `tor` must be on the `PATH` or `-tor.path` must be set to the path of the `tor` executable. Even with those flags,
|
||||
only the integration tests that do not connect to the Tor network are run. To also include the tests that use the Tor
|
||||
network, add the `-tor.network` flag. For details Tor logs during any of the integration tests, use the `-tor.verbose`
|
||||
flag.
|
|
@ -33,7 +33,7 @@ func TestMain(m *testing.M) {
|
|||
|
||||
func GlobalEnabledNetworkContext(t *testing.T) *TestContext {
|
||||
if !torEnabled || !torIncludeNetworkTests {
|
||||
t.Skip("Only runs if -tor and -tor.network is set")
|
||||
t.Skip("Only runs if -tor and -tor.network are set")
|
||||
}
|
||||
if globalEnabledNetworkContext == nil {
|
||||
ctx := NewTestContext(t, nil)
|
||||
|
|
|
@ -16,8 +16,8 @@ import (
|
|||
var serviceIDEncoding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
// OnionServiceIDFromPrivateKey generates the onion service ID from the given
|
||||
// private key. This panics if the private key is not a crypto/*rsa.PrivateKey
|
||||
// or github.com/cretz/bine/torutil/ed25519.KeyPair.
|
||||
// private key. This panics if the private key is not a 1024-bit
|
||||
// crypto/*rsa.PrivateKey or github.com/cretz/bine/torutil/ed25519.KeyPair.
|
||||
func OnionServiceIDFromPrivateKey(key crypto.PrivateKey) string {
|
||||
switch k := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
|
@ -29,8 +29,8 @@ func OnionServiceIDFromPrivateKey(key crypto.PrivateKey) string {
|
|||
}
|
||||
|
||||
// OnionServiceIDFromPublicKey generates the onion service ID from the given
|
||||
// public key. This panics if the public key is not a crypto/*rsa.PublicKey or
|
||||
// github.com/cretz/bine/torutil/ed25519.PublicKey.
|
||||
// public key. This panics if the public key is not a 1024-bit
|
||||
// crypto/*rsa.PublicKey or github.com/cretz/bine/torutil/ed25519.PublicKey.
|
||||
func OnionServiceIDFromPublicKey(key crypto.PublicKey) string {
|
||||
switch k := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
|
@ -38,12 +38,15 @@ func OnionServiceIDFromPublicKey(key crypto.PublicKey) string {
|
|||
case ed25519.PublicKey:
|
||||
return OnionServiceIDFromV3PublicKey(k)
|
||||
}
|
||||
panic(fmt.Sprintf("Unrecognized private key type: %T", key))
|
||||
panic(fmt.Sprintf("Unrecognized public key type: %T", key))
|
||||
}
|
||||
|
||||
// OnionServiceIDFromV2PublicKey generates a V2 service ID for the given
|
||||
// RSA-1024 public key.
|
||||
// RSA-1024 public key. Panics if not a 1024-bit key.
|
||||
func OnionServiceIDFromV2PublicKey(key *rsa.PublicKey) string {
|
||||
if key.N.BitLen() != 1024 {
|
||||
panic("RSA key not 1024 bit")
|
||||
}
|
||||
h := sha1.New()
|
||||
h.Write(x509.MarshalPKCS1PublicKey(key))
|
||||
return strings.ToLower(serviceIDEncoding.EncodeToString(h.Sum(nil)[:10]))
|
||||
|
@ -60,3 +63,23 @@ func OnionServiceIDFromV3PublicKey(key ed25519.PublicKey) string {
|
|||
keyBytes[34] = 0x03
|
||||
return strings.ToLower(serviceIDEncoding.EncodeToString(keyBytes[:]))
|
||||
}
|
||||
|
||||
// PublicKeyFromV3OnionServiceID returns a public key for the given service ID
|
||||
// or an error if the service ID is invalid.
|
||||
func PublicKeyFromV3OnionServiceID(id string) (ed25519.PublicKey, error) {
|
||||
byts, err := serviceIDEncoding.DecodeString(strings.ToUpper(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(byts) != 35 {
|
||||
return nil, fmt.Errorf("Invalid id length")
|
||||
} else if byts[34] != 0x03 {
|
||||
return nil, fmt.Errorf("Invalid version")
|
||||
}
|
||||
// Do a checksum check
|
||||
key := ed25519.PublicKey(byts[:32])
|
||||
checkSum := sha3.Sum256(append(append([]byte(".onion checksum"), key...), 0x03))
|
||||
if byts[32] != checkSum[0] || byts[33] != checkSum[1] {
|
||||
return nil, fmt.Errorf("Invalid checksum")
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package torutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/cretz/bine/torutil/ed25519"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func genRsa(t *testing.T, bits int) *rsa.PrivateKey {
|
||||
k, e := rsa.GenerateKey(rand.Reader, bits)
|
||||
require.NoError(t, e)
|
||||
return k
|
||||
}
|
||||
|
||||
func genEd25519(t *testing.T) ed25519.KeyPair {
|
||||
k, e := ed25519.GenerateKey(nil)
|
||||
require.NoError(t, e)
|
||||
return k
|
||||
}
|
||||
|
||||
func TestOnionServiceIDFromPrivateKey(t *testing.T) {
|
||||
assert := func(key interface{}, shouldPanic bool) {
|
||||
if shouldPanic {
|
||||
require.Panics(t, func() { OnionServiceIDFromPrivateKey(key) })
|
||||
} else {
|
||||
require.NotPanics(t, func() { OnionServiceIDFromPrivateKey(key) })
|
||||
}
|
||||
}
|
||||
assert(nil, true)
|
||||
assert("bad type", true)
|
||||
assert(genRsa(t, 512), true)
|
||||
assert(genRsa(t, 1024), false)
|
||||
assert(genEd25519(t), false)
|
||||
}
|
||||
|
||||
func TestOnionServiceIDFromPublicKey(t *testing.T) {
|
||||
assert := func(key interface{}, shouldPanic bool) {
|
||||
if shouldPanic {
|
||||
require.Panics(t, func() { OnionServiceIDFromPublicKey(key) })
|
||||
} else {
|
||||
require.NotPanics(t, func() { OnionServiceIDFromPublicKey(key) })
|
||||
}
|
||||
}
|
||||
assert(nil, true)
|
||||
assert("bad type", true)
|
||||
assert(genRsa(t, 512).Public(), true)
|
||||
assert(genRsa(t, 1024), true)
|
||||
assert(genRsa(t, 1024).Public(), false)
|
||||
assert(genEd25519(t), true)
|
||||
assert(genEd25519(t).Public(), false)
|
||||
}
|
||||
|
||||
func TestOnionServiceIDFromV2PublicKey(t *testing.T) {
|
||||
keyNs := []string{
|
||||
// Good:
|
||||
"146324450904690776677821409274235322093351159271459646966603918354799061259062657420293876128692345182038558188684966983800327732948675482476772223940488746110835191444662533167597590461666544121677987412778085089886835490778554764504249900341150942052002951429704745527158573712253866271451928082512868548761",
|
||||
"128765593328258418045179848773717342016715415508670816023595649916344640363150769867803188216600032423256896646578968925175093584252663716464819945657362904808776223191437446288878991355616138261909405164010386485361833909203128674413041630645284466111155610996017814867550636519109125461589251061597106654453",
|
||||
"142816677715940797613971689484332187730646681999601531244837211468050734148365138492918019219363903243436898624689103727294808675158556496441738627693945143098034304873441312947712853824963023184593797741228534339590785521072446422663170639163836372239933736851693970208563926767141632739068954958552435402293",
|
||||
"145115352997770387235216741368218582671004692828327605746752722031765658311649572143281396789387808261614671508963042791801662334421789227429337599249357503724975792005849908733936522427330824294880823009884401313371327997012363609954851207630328042324027016587584799514594101157535904741483269310276131442141",
|
||||
"147719637109219630754585551462675301139659936682064979504052824885582296579356301771435242063159743126441027484306731955552256555531866636211668612294755914990702770530441483651548916585382488692381916953093261634746890673551241873307767188168965986976533243218915179497387875035829308609534245761833108189053",
|
||||
// Bad (512 bit):
|
||||
"12406459612976799354275054531003074054562219068852891594185203203668633138039185159716483674833390801567933368800574140712590387835931746258315639847176501",
|
||||
}
|
||||
matchingIDs := []string{
|
||||
"kqxsrkmm272hqvbj",
|
||||
"75rzoc3nxzucidqb",
|
||||
"l2vxsdecx6yita6r",
|
||||
"hzma3bmo7mtyr5mq",
|
||||
"prek6tayypvteljb",
|
||||
"",
|
||||
}
|
||||
for i, keyN := range keyNs {
|
||||
pubKey := &rsa.PublicKey{E: 65537, N: new(big.Int)}
|
||||
pubKey.N, _ = pubKey.N.SetString(keyN, 10)
|
||||
matchingID := matchingIDs[i]
|
||||
if matchingID == "" {
|
||||
require.Panics(t, func() { OnionServiceIDFromV2PublicKey(pubKey) })
|
||||
} else {
|
||||
require.Equal(t, matchingID, OnionServiceIDFromV2PublicKey(pubKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnionServiceIDFromV3PublicKey(t *testing.T) {
|
||||
base64Keys := []string{
|
||||
"SLne6D/uawqUj23619GbeYCd6HnzYPqyUvF8/xyz/3XNVpkgnonQI+J5NQVSGkppD1b0M87+qOtUBmVXsd7H3w",
|
||||
"kPUs5aPoqISZVbg0q7coW+mNCODlcL4O7k2QWFOCC0gOQBiDm+g4Xz48lqucA7o2HIQ3gBdL5rlB6+q1tFdJwQ",
|
||||
"YGzw/EwpcqfWb5UWIw652Ps4vTKu38VgX7Qo16XvOWjNWQK9YmfgARYiGQ1XYXEAKBJvoq8x+rKFbQN3FG1F6w",
|
||||
"IJIZcWE57n5WCvHU2x7GkpBCIw0S0vWd+QyrE5RifGwPtYsbtxjyOxlb754Z0zXLZc+yQUp9hMQt5dt/YNpMag",
|
||||
"SD7d4I6ZOjNlcqR2g4ptFJUw0tUHPQvfk92sExvnJ1uofPw9T9LUaaEs3rE/1yoGWKI4YejAzaTJXF9wrWQyuA",
|
||||
}
|
||||
matchingIDs := []string{
|
||||
"2s2wk473fmotzgh6l2ycigrwegnurlzufatjm3bglrb36zbvlerskxad",
|
||||
"tmcpdbgklpbywqyjpr7fijvjl7qjihd7pyubosbeohefec2m2thvzoqd",
|
||||
"nrcan5uye2fwazixubug6pzrzp6ofjez43bjcyfoxhgxyygxbhgs4zqd",
|
||||
"g2csv4kavhunvs45vxxc5ljz775d5a4ycqo4m4nrwpk3b4gryvz2zdyd",
|
||||
"jviaiibaz7r6wqxttj5i2bi4zjfilmsevplwwtxdfyjph2sdmq5osdid",
|
||||
}
|
||||
for i, base64Key := range base64Keys {
|
||||
key, err := base64.RawStdEncoding.DecodeString(base64Key)
|
||||
require.NoError(t, err)
|
||||
pubKey := ed25519.PrivateKey(key).PublicKey()
|
||||
matchingID := matchingIDs[i]
|
||||
require.Equal(t, matchingID, OnionServiceIDFromV3PublicKey(pubKey))
|
||||
// Check verify here too
|
||||
derivedPubKey, err := PublicKeyFromV3OnionServiceID(matchingID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pubKey, derivedPubKey)
|
||||
// Let's mangle the matchingID a bit
|
||||
tooLong := matchingID + "ddddd"
|
||||
_, err = PublicKeyFromV3OnionServiceID(tooLong)
|
||||
require.EqualError(t, err, "Invalid id length")
|
||||
badVersion := matchingID[:len(matchingID)-1] + "e"
|
||||
_, err = PublicKeyFromV3OnionServiceID(badVersion)
|
||||
require.EqualError(t, err, "Invalid version")
|
||||
badChecksum := []byte(matchingID)
|
||||
badChecksum[len(badChecksum)-3] = 'q'
|
||||
_, err = PublicKeyFromV3OnionServiceID(string(badChecksum))
|
||||
require.EqualError(t, err, "Invalid checksum")
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ var simpleQuotedStringEscapeReplacer = strings.NewReplacer(
|
|||
// EscapeSimpleQuotedString calls EscapeSimpleQuotedStringContents and then
|
||||
// surrounds the entire string with double quotes.
|
||||
func EscapeSimpleQuotedString(str string) string {
|
||||
return "\"" + simpleQuotedStringEscapeReplacer.Replace(str) + "\""
|
||||
return "\"" + EscapeSimpleQuotedStringContents(str) + "\""
|
||||
}
|
||||
|
||||
// EscapeSimpleQuotedStringContents escapes backslashes, double quotes,
|
||||
|
@ -74,7 +74,7 @@ func UnescapeSimpleQuotedString(str string) (string, error) {
|
|||
}
|
||||
|
||||
// UnescapeSimpleQuotedStringContents unescapes backslashes, double quotes,
|
||||
// newlines, and carriage returns.
|
||||
// newlines, and carriage returns. Also errors if those aren't escaped.
|
||||
func UnescapeSimpleQuotedStringContents(str string) (string, error) {
|
||||
ret := ""
|
||||
escaping := false
|
||||
|
@ -91,6 +91,8 @@ func UnescapeSimpleQuotedStringContents(str string) (string, error) {
|
|||
}
|
||||
ret += "\""
|
||||
escaping = false
|
||||
case '\r', '\n':
|
||||
return "", fmt.Errorf("Unescaped newline or carriage return")
|
||||
default:
|
||||
if escaping {
|
||||
if c == 'r' {
|
||||
|
@ -103,6 +105,7 @@ func UnescapeSimpleQuotedStringContents(str string) (string, error) {
|
|||
} else {
|
||||
ret += string(c)
|
||||
}
|
||||
escaping = false
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package torutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPartitionString(t *testing.T) {
|
||||
assert := func(str string, ch byte, expectedA string, expectedB string, expectedOk bool) {
|
||||
a, b, ok := PartitionString(str, ch)
|
||||
require.Equal(t, expectedA, a)
|
||||
require.Equal(t, expectedB, b)
|
||||
require.Equal(t, expectedOk, ok)
|
||||
}
|
||||
assert("foo:bar", ':', "foo", "bar", true)
|
||||
assert(":bar", ':', "", "bar", true)
|
||||
assert("foo:", ':', "foo", "", true)
|
||||
assert("foo", ':', "foo", "", false)
|
||||
assert("foo:bar:baz", ':', "foo", "bar:baz", true)
|
||||
}
|
||||
|
||||
func TestPartitionStringFromEnd(t *testing.T) {
|
||||
assert := func(str string, ch byte, expectedA string, expectedB string, expectedOk bool) {
|
||||
a, b, ok := PartitionStringFromEnd(str, ch)
|
||||
require.Equal(t, expectedA, a)
|
||||
require.Equal(t, expectedB, b)
|
||||
require.Equal(t, expectedOk, ok)
|
||||
}
|
||||
assert("foo:bar", ':', "foo", "bar", true)
|
||||
assert(":bar", ':', "", "bar", true)
|
||||
assert("foo:", ':', "foo", "", true)
|
||||
assert("foo", ':', "foo", "", false)
|
||||
assert("foo:bar:baz", ':', "foo:bar", "baz", true)
|
||||
}
|
||||
|
||||
func TestEscapeSimpleQuotedStringIfNeeded(t *testing.T) {
|
||||
assert := func(str string, shouldBeDiff bool) {
|
||||
maybeEscaped := EscapeSimpleQuotedStringIfNeeded(str)
|
||||
if shouldBeDiff {
|
||||
require.NotEqual(t, str, maybeEscaped)
|
||||
} else {
|
||||
require.Equal(t, str, maybeEscaped)
|
||||
}
|
||||
}
|
||||
assert("foo", false)
|
||||
assert(" foo", true)
|
||||
assert("f\\oo", true)
|
||||
assert("fo\"o", true)
|
||||
assert("f\roo", true)
|
||||
assert("fo\no", true)
|
||||
}
|
||||
|
||||
func TestEscapeSimpleQuotedString(t *testing.T) {
|
||||
require.Equal(t, "\"foo\"", EscapeSimpleQuotedString("foo"))
|
||||
}
|
||||
|
||||
func TestEscapeSimpleQuotedStringContents(t *testing.T) {
|
||||
assert := func(str string, expected string) {
|
||||
require.Equal(t, expected, EscapeSimpleQuotedStringContents(str))
|
||||
}
|
||||
assert("foo", "foo")
|
||||
assert("f\\oo", "f\\\\oo")
|
||||
assert("f\\noo", "f\\\\noo")
|
||||
assert("f\n o\ro", "f\\n o\\ro")
|
||||
assert("fo\r\\\"o", "fo\\r\\\\\\\"o")
|
||||
}
|
||||
|
||||
func TestUnescapeSimpleQuotedStringIfNeeded(t *testing.T) {
|
||||
assert := func(str string, expectedStr string, expectedErr bool) {
|
||||
actualStr, actualErr := UnescapeSimpleQuotedStringIfNeeded(str)
|
||||
require.Equal(t, expectedStr, actualStr)
|
||||
require.Equal(t, expectedErr, actualErr != nil)
|
||||
}
|
||||
assert("foo", "foo", false)
|
||||
assert("\"foo\"", "foo", false)
|
||||
assert("\"f\"oo\"", "", true)
|
||||
}
|
||||
|
||||
func TestUnescapeSimpleQuotedString(t *testing.T) {
|
||||
assert := func(str string, expectedStr string, expectedErr bool) {
|
||||
actualStr, actualErr := UnescapeSimpleQuotedString(str)
|
||||
require.Equal(t, expectedStr, actualStr)
|
||||
require.Equal(t, expectedErr, actualErr != nil)
|
||||
}
|
||||
assert("foo", "", true)
|
||||
assert("\"foo\"", "foo", false)
|
||||
assert("\"f\"oo\"", "", true)
|
||||
}
|
||||
|
||||
func TestUnescapeSimpleQuotedStringContents(t *testing.T) {
|
||||
assert := func(str string, expectedStr string, expectedErr bool) {
|
||||
actualStr, actualErr := UnescapeSimpleQuotedStringContents(str)
|
||||
require.Equal(t, expectedStr, actualStr)
|
||||
require.Equal(t, expectedErr, actualErr != nil)
|
||||
}
|
||||
assert("foo", "foo", false)
|
||||
assert("f\\\\oo", "f\\oo", false)
|
||||
assert("f\\\\noo", "f\\noo", false)
|
||||
assert("f\\n o\\ro", "f\n o\ro", false)
|
||||
assert("fo\\r\\\\\\\"o", "fo\r\\\"o", false)
|
||||
assert("f\"oo", "", true)
|
||||
assert("f\roo", "", true)
|
||||
assert("f\noo", "", true)
|
||||
assert("f\\oo", "", true)
|
||||
}
|
Loading…
Reference in New Issue