bine/torutil/key.go

86 lines
2.8 KiB
Go

package torutil
import (
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base32"
"fmt"
"strings"
"git.openprivacy.ca/openprivacy/bine/torutil/ed25519"
"golang.org/x/crypto/sha3"
)
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 1024-bit
// crypto/*rsa.PrivateKey or git.openprivacy.ca/openprivacy/bine/torutil/ed25519.KeyPair.
func OnionServiceIDFromPrivateKey(key crypto.PrivateKey) string {
switch k := key.(type) {
case *rsa.PrivateKey:
return OnionServiceIDFromV2PublicKey(&k.PublicKey)
case ed25519.KeyPair:
return OnionServiceIDFromV3PublicKey(k.PublicKey())
}
panic(fmt.Sprintf("Unrecognized private key type: %T", key))
}
// OnionServiceIDFromPublicKey generates the onion service ID from the given
// public key. This panics if the public key is not a 1024-bit
// crypto/*rsa.PublicKey or git.openprivacy.ca/openprivacy/bine/torutil/ed25519.PublicKey.
func OnionServiceIDFromPublicKey(key crypto.PublicKey) string {
switch k := key.(type) {
case *rsa.PublicKey:
return OnionServiceIDFromV2PublicKey(k)
case ed25519.PublicKey:
return OnionServiceIDFromV3PublicKey(k)
}
panic(fmt.Sprintf("Unrecognized public key type: %T", key))
}
// OnionServiceIDFromV2PublicKey generates a V2 service ID for the given
// 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]))
}
// OnionServiceIDFromV3PublicKey generates a V3 service ID for the given
// ED25519 public key.
func OnionServiceIDFromV3PublicKey(key ed25519.PublicKey) string {
checkSum := sha3.Sum256(append(append([]byte(".onion checksum"), key...), 0x03))
var keyBytes [35]byte
copy(keyBytes[:], key)
keyBytes[32] = checkSum[0]
keyBytes[33] = checkSum[1]
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
}