go.crypto: initial code
Manual edits to README.
Moved from main Go repository, deleted Makefiles, ran gofix -r go1rename.
Tested with: go test code.google.com/p/go.crypto/...
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5564059
diff --git a/README b/README
index bc954b0..f1e0cbf 100644
--- a/README
+++ b/README
@@ -1,16 +1,3 @@
-This is an empty Go subrepository. To create a new subrepository,
-visit http://code.google.com/p/go/adminSource and under repositories,
-type the new name, check [x] Clone contents and select [empty] as
-the one you want to clone.
+This repository holds supplementary Go cryptography libraries.
-Then execute:
- go get code.google.com/p/go.newrepo
- cd $(go list -e -f '{{.Dir}}' code.google.com/p/go.newrepo)
-
-The go get will complain about not finding source code, but it will
-successfully check out the repository.
-
-Edit the README (this file) to describe the new subrepository, and then
-use the usual hg change, mail, submit to send in the change.
-You will need to follow http://golang.org/doc/contribute.html#Code_review
-to enable the Code Review extension (pointing into your Go root).
+To submit changes to this repository, see http://golang.org/doc/contribute.html.
diff --git a/bcrypt/base64.go b/bcrypt/base64.go
new file mode 100644
index 0000000..fc31160
--- /dev/null
+++ b/bcrypt/base64.go
@@ -0,0 +1,35 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bcrypt
+
+import "encoding/base64"
+
+const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+
+var bcEncoding = base64.NewEncoding(alphabet)
+
+func base64Encode(src []byte) []byte {
+ n := bcEncoding.EncodedLen(len(src))
+ dst := make([]byte, n)
+ bcEncoding.Encode(dst, src)
+ for dst[n-1] == '=' {
+ n--
+ }
+ return dst[:n]
+}
+
+func base64Decode(src []byte) ([]byte, error) {
+ numOfEquals := 4 - (len(src) % 4)
+ for i := 0; i < numOfEquals; i++ {
+ src = append(src, '=')
+ }
+
+ dst := make([]byte, bcEncoding.DecodedLen(len(src)))
+ n, err := bcEncoding.Decode(dst, src)
+ if err != nil {
+ return nil, err
+ }
+ return dst[:n], nil
+}
diff --git a/bcrypt/bcrypt.go b/bcrypt/bcrypt.go
new file mode 100644
index 0000000..d1ebee8
--- /dev/null
+++ b/bcrypt/bcrypt.go
@@ -0,0 +1,282 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
+// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
+package bcrypt
+
+// The code is a port of Provos and Mazières's C implementation.
+import (
+ "code.google.com/p/go.crypto/blowfish"
+ "crypto/rand"
+ "crypto/subtle"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+)
+
+const (
+ MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword
+ MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
+ DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
+)
+
+// The error returned from CompareHashAndPassword when a password and hash do
+// not match.
+var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
+
+// The error returned from CompareHashAndPassword when a hash is too short to
+// be a bcrypt hash.
+var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
+
+// The error returned from CompareHashAndPassword when a hash was created with
+// a bcrypt algorithm newer than this implementation.
+type HashVersionTooNewError byte
+
+func (hv HashVersionTooNewError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
+}
+
+// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
+type InvalidHashPrefixError byte
+
+func (ih InvalidHashPrefixError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
+}
+
+type InvalidCostError int
+
+func (ic InvalidCostError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost))
+}
+
+const (
+ majorVersion = '2'
+ minorVersion = 'a'
+ maxSaltSize = 16
+ maxCryptedHashSize = 23
+ encodedSaltSize = 22
+ encodedHashSize = 31
+ minHashSize = 59
+)
+
+// magicCipherData is an IV for the 64 Blowfish encryption calls in
+// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
+var magicCipherData = []byte{
+ 0x4f, 0x72, 0x70, 0x68,
+ 0x65, 0x61, 0x6e, 0x42,
+ 0x65, 0x68, 0x6f, 0x6c,
+ 0x64, 0x65, 0x72, 0x53,
+ 0x63, 0x72, 0x79, 0x44,
+ 0x6f, 0x75, 0x62, 0x74,
+}
+
+type hashed struct {
+ hash []byte
+ salt []byte
+ cost uint32 // allowed range is MinCost to MaxCost
+ major byte
+ minor byte
+}
+
+// GenerateFromPassword returns the bcrypt hash of the password at the given
+// cost. If the cost given is less than MinCost, the cost will be set to
+// MinCost, instead. Use CompareHashAndPassword, as defined in this package,
+// to compare the returned hashed password with its cleartext version.
+func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
+ p, err := newFromPassword(password, cost)
+ if err != nil {
+ return nil, err
+ }
+ return p.Hash(), nil
+}
+
+// CompareHashAndPassword compares a bcrypt hashed password with its possible
+// plaintext equivalent. Note: Using bytes.Equal for this job is
+// insecure. Returns nil on success, or an error on failure.
+func CompareHashAndPassword(hashedPassword, password []byte) error {
+ p, err := newFromHash(hashedPassword)
+ if err != nil {
+ return err
+ }
+
+ otherHash, err := bcrypt(password, p.cost, p.salt)
+ if err != nil {
+ return err
+ }
+
+ otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
+ if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
+ return nil
+ }
+
+ return ErrMismatchedHashAndPassword
+}
+
+func newFromPassword(password []byte, cost int) (*hashed, error) {
+ if cost < MinCost {
+ cost = DefaultCost
+ }
+ p := new(hashed)
+ p.major = majorVersion
+ p.minor = minorVersion
+
+ err := checkCost(cost)
+ if err != nil {
+ return nil, err
+ }
+ p.cost = uint32(cost)
+
+ unencodedSalt := make([]byte, maxSaltSize)
+ _, err = io.ReadFull(rand.Reader, unencodedSalt)
+ if err != nil {
+ return nil, err
+ }
+
+ p.salt = base64Encode(unencodedSalt)
+ hash, err := bcrypt(password, p.cost, p.salt)
+ if err != nil {
+ return nil, err
+ }
+ p.hash = hash
+ return p, err
+}
+
+func newFromHash(hashedSecret []byte) (*hashed, error) {
+ if len(hashedSecret) < minHashSize {
+ return nil, ErrHashTooShort
+ }
+ p := new(hashed)
+ n, err := p.decodeVersion(hashedSecret)
+ if err != nil {
+ return nil, err
+ }
+ hashedSecret = hashedSecret[n:]
+ n, err = p.decodeCost(hashedSecret)
+ if err != nil {
+ return nil, err
+ }
+ hashedSecret = hashedSecret[n:]
+
+ // The "+2" is here because we'll have to append at most 2 '=' to the salt
+ // when base64 decoding it in expensiveBlowfishSetup().
+ p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
+ copy(p.salt, hashedSecret[:encodedSaltSize])
+
+ hashedSecret = hashedSecret[encodedSaltSize:]
+ p.hash = make([]byte, len(hashedSecret))
+ copy(p.hash, hashedSecret)
+
+ return p, nil
+}
+
+func bcrypt(password []byte, cost uint32, salt []byte) ([]byte, error) {
+ cipherData := make([]byte, len(magicCipherData))
+ copy(cipherData, magicCipherData)
+
+ c, err := expensiveBlowfishSetup(password, cost, salt)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 24; i += 8 {
+ for j := 0; j < 64; j++ {
+ c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
+ }
+ }
+
+ // Bug compatibility with C bcrypt implementations. We only encode 23 of
+ // the 24 bytes encrypted.
+ hsh := base64Encode(cipherData[:maxCryptedHashSize])
+ return hsh, nil
+}
+
+func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
+
+ csalt, err := base64Decode(salt)
+ if err != nil {
+ return nil, err
+ }
+
+ // Bug compatibility with C bcrypt implementations. They use the trailing
+ // NULL in the key string during expansion.
+ ckey := append(key, 0)
+
+ c, err := blowfish.NewSaltedCipher(ckey, csalt)
+ if err != nil {
+ return nil, err
+ }
+
+ rounds := 1 << cost
+ for i := 0; i < rounds; i++ {
+ blowfish.ExpandKey(ckey, c)
+ blowfish.ExpandKey(csalt, c)
+ }
+
+ return c, nil
+}
+
+func (p *hashed) Hash() []byte {
+ arr := make([]byte, 60)
+ arr[0] = '$'
+ arr[1] = p.major
+ n := 2
+ if p.minor != 0 {
+ arr[2] = p.minor
+ n = 3
+ }
+ arr[n] = '$'
+ n += 1
+ copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
+ n += 2
+ arr[n] = '$'
+ n += 1
+ copy(arr[n:], p.salt)
+ n += encodedSaltSize
+ copy(arr[n:], p.hash)
+ n += encodedHashSize
+ return arr[:n]
+}
+
+func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
+ if sbytes[0] != '$' {
+ return -1, InvalidHashPrefixError(sbytes[0])
+ }
+ if sbytes[1] > majorVersion {
+ return -1, HashVersionTooNewError(sbytes[1])
+ }
+ p.major = sbytes[1]
+ n := 3
+ if sbytes[2] != '$' {
+ p.minor = sbytes[2]
+ n++
+ }
+ return n, nil
+}
+
+// sbytes should begin where decodeVersion left off.
+func (p *hashed) decodeCost(sbytes []byte) (int, error) {
+ cost, err := strconv.Atoi(string(sbytes[0:2]))
+ if err != nil {
+ return -1, err
+ }
+ err = checkCost(cost)
+ if err != nil {
+ return -1, err
+ }
+ p.cost = uint32(cost)
+ return 3, nil
+}
+
+func (p *hashed) String() string {
+ return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
+}
+
+func checkCost(cost int) error {
+ if cost < MinCost || cost > MaxCost {
+ return InvalidCostError(cost)
+ }
+ return nil
+}
diff --git a/bcrypt/bcrypt_test.go b/bcrypt/bcrypt_test.go
new file mode 100644
index 0000000..9ad5c1c
--- /dev/null
+++ b/bcrypt/bcrypt_test.go
@@ -0,0 +1,194 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bcrypt
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestBcryptingIsEasy(t *testing.T) {
+ pass := []byte("mypassword")
+ hp, err := GenerateFromPassword(pass, 0)
+ if err != nil {
+ t.Fatalf("GenerateFromPassword error: %s", err)
+ }
+
+ if CompareHashAndPassword(hp, pass) != nil {
+ t.Errorf("%v should hash %s correctly", hp, pass)
+ }
+
+ notPass := "notthepass"
+ err = CompareHashAndPassword(hp, []byte(notPass))
+ if err != ErrMismatchedHashAndPassword {
+ t.Errorf("%v and %s should be mismatched", hp, notPass)
+ }
+}
+
+func TestBcryptingIsCorrect(t *testing.T) {
+ pass := []byte("allmine")
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+
+ hash, err := bcrypt(pass, 10, salt)
+ if err != nil {
+ t.Fatalf("bcrypt blew up: %v", err)
+ }
+ if !bytes.HasSuffix(expectedHash, hash) {
+ t.Errorf("%v should be the suffix of %v", hash, expectedHash)
+ }
+
+ h, err := newFromHash(expectedHash)
+ if err != nil {
+ t.Errorf("Unable to parse %s: %v", string(expectedHash), err)
+ }
+
+ // This is not the safe way to compare these hashes. We do this only for
+ // testing clarity. Use bcrypt.CompareHashAndPassword()
+ if err == nil && !bytes.Equal(expectedHash, h.Hash()) {
+ t.Errorf("Parsed hash %v should equal %v", h.Hash(), expectedHash)
+ }
+}
+
+func TestTooLongPasswordsWork(t *testing.T) {
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ // One byte over the usual 56 byte limit that blowfish has
+ tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456")
+ tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C")
+ hash, err := bcrypt(tooLongPass, 10, salt)
+ if err != nil {
+ t.Fatalf("bcrypt blew up on long password: %v", err)
+ }
+ if !bytes.HasSuffix(tooLongExpected, hash) {
+ t.Errorf("%v should be the suffix of %v", hash, tooLongExpected)
+ }
+}
+
+type InvalidHashTest struct {
+ err error
+ hash []byte
+}
+
+var invalidTests = []InvalidHashTest{
+ {ErrHashTooShort, []byte("$2a$10$fooo")},
+ {ErrHashTooShort, []byte("$2a")},
+ {HashVersionTooNewError('3'), []byte("$3a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+ {InvalidHashPrefixError('%'), []byte("%2a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+ {InvalidCostError(32), []byte("$2a$32$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+}
+
+func TestInvalidHashErrors(t *testing.T) {
+ check := func(name string, expected, err error) {
+ if err == nil {
+ t.Errorf("%s: Should have returned an error", name)
+ }
+ if err != nil && err != expected {
+ t.Errorf("%s gave err %v but should have given %v", name, err, expected)
+ }
+ }
+ for _, iht := range invalidTests {
+ _, err := newFromHash(iht.hash)
+ check("newFromHash", iht.err, err)
+ err = CompareHashAndPassword(iht.hash, []byte("anything"))
+ check("CompareHashAndPassword", iht.err, err)
+ }
+}
+
+func TestUnpaddedBase64Encoding(t *testing.T) {
+ original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30}
+ encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe")
+
+ encoded := base64Encode(original)
+
+ if !bytes.Equal(encodedOriginal, encoded) {
+ t.Errorf("Encoded %v should have equaled %v", encoded, encodedOriginal)
+ }
+
+ decoded, err := base64Decode(encodedOriginal)
+ if err != nil {
+ t.Fatalf("base64Decode blew up: %s", err)
+ }
+
+ if !bytes.Equal(decoded, original) {
+ t.Errorf("Decoded %v should have equaled %v", decoded, original)
+ }
+}
+
+func TestCost(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+
+ pass := []byte("mypassword")
+
+ for c := 0; c < MinCost; c++ {
+ p, _ := newFromPassword(pass, c)
+ if p.cost != uint32(DefaultCost) {
+ t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost)
+ }
+ }
+
+ p, _ := newFromPassword(pass, 14)
+ if p.cost != 14 {
+ t.Errorf("newFromPassword should default cost to 14, but was %d", p.cost)
+ }
+
+ hp, _ := newFromHash(p.Hash())
+ if p.cost != hp.cost {
+ t.Errorf("newFromHash should maintain the cost at %d, but was %d", p.cost, hp.cost)
+ }
+
+ _, err := newFromPassword(pass, 32)
+ if err == nil {
+ t.Fatalf("newFromPassword: should return a cost error")
+ }
+ if err != InvalidCostError(32) {
+ t.Errorf("newFromPassword: should return cost error, got %#v", err)
+ }
+}
+
+func TestCostReturnsWithLeadingZeroes(t *testing.T) {
+ hp, _ := newFromPassword([]byte("abcdefgh"), 7)
+ cost := hp.Hash()[4:7]
+ expected := []byte("07$")
+
+ if !bytes.Equal(expected, cost) {
+ t.Errorf("single digit costs in hash should have leading zeros: was %v instead of %v", cost, expected)
+ }
+}
+
+func TestMinorNotRequired(t *testing.T) {
+ noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+ h, err := newFromHash(noMinorHash)
+ if err != nil {
+ t.Fatalf("No minor hash blew up: %s", err)
+ }
+ if h.minor != 0 {
+ t.Errorf("Should leave minor version at 0, but was %d", h.minor)
+ }
+
+ if !bytes.Equal(noMinorHash, h.Hash()) {
+ t.Errorf("Should generate hash %v, but created %v", noMinorHash, h.Hash())
+ }
+}
+
+func BenchmarkEqual(b *testing.B) {
+ b.StopTimer()
+ passwd := []byte("somepasswordyoulike")
+ hash, _ := GenerateFromPassword(passwd, 10)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ CompareHashAndPassword(hash, passwd)
+ }
+}
+
+func BenchmarkGeneration(b *testing.B) {
+ b.StopTimer()
+ passwd := []byte("mylongpassword1234")
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ GenerateFromPassword(passwd, 10)
+ }
+}
diff --git a/blowfish/block.go b/blowfish/block.go
new file mode 100644
index 0000000..326292d
--- /dev/null
+++ b/blowfish/block.go
@@ -0,0 +1,192 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package blowfish
+
+// ExpandKey performs a key expansion on the given *Cipher. Specifically, it
+// performs the Blowfish algorithm's key schedule which sets up the *Cipher's
+// pi and substitution tables for calls to Encrypt. This is used, primarily,
+// by the bcrypt package to reuse the Blowfish key schedule during its
+// set up. It's unlikely that you need to use this directly.
+func ExpandKey(key []byte, c *Cipher) {
+ j := 0
+ for i := 0; i < 18; i++ {
+ var d uint32
+ for k := 0; k < 4; k++ {
+ d = d<<8 | uint32(key[j])&0x000000FF
+ j++
+ if j >= len(key) {
+ j = 0
+ }
+ }
+ c.p[i] ^= d
+ }
+
+ var l, r uint32
+ for i := 0; i < 18; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.p[i], c.p[i+1] = l, r
+ }
+
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s0[i], c.s0[i+1] = l, r
+ }
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s1[i], c.s1[i+1] = l, r
+ }
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s2[i], c.s2[i+1] = l, r
+ }
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s3[i], c.s3[i+1] = l, r
+ }
+}
+
+// This is similar to ExpandKey, but folds the salt during the key
+// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero
+// salt passed in, reusing ExpandKey turns out to be a place of inefficiency
+// and specializing it here is useful.
+func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) {
+ j := 0
+ expandedKey := make([]uint32, 18)
+ for i := 0; i < 18; i++ {
+ var d uint32
+ for k := 0; k < 4; k++ {
+ d = d<<8 | uint32(key[j])&0x000000FF
+ j++
+ if j >= len(key) {
+ j = 0
+ }
+ }
+ expandedKey[i] = d
+ c.p[i] ^= d
+ }
+
+ j = 0
+ expandedSalt := make([]uint32, 18)
+ for i := 0; i < 18; i++ {
+ var d uint32
+ for k := 0; k < 4; k++ {
+ d = d<<8 | uint32(salt[j])&0x000000FF
+ j++
+ if j >= len(salt) {
+ j = 0
+ }
+ }
+ expandedSalt[i] = d
+ }
+
+ var l, r uint32
+ for i := 0; i < 18; i += 2 {
+ l ^= expandedSalt[i&2]
+ r ^= expandedSalt[(i&2)+1]
+ l, r = encryptBlock(l, r, c)
+ c.p[i], c.p[i+1] = l, r
+ }
+
+ for i := 0; i < 256; i += 4 {
+ l ^= expandedSalt[2]
+ r ^= expandedSalt[3]
+ l, r = encryptBlock(l, r, c)
+ c.s0[i], c.s0[i+1] = l, r
+
+ l ^= expandedSalt[0]
+ r ^= expandedSalt[1]
+ l, r = encryptBlock(l, r, c)
+ c.s0[i+2], c.s0[i+3] = l, r
+
+ }
+
+ for i := 0; i < 256; i += 4 {
+ l ^= expandedSalt[2]
+ r ^= expandedSalt[3]
+ l, r = encryptBlock(l, r, c)
+ c.s1[i], c.s1[i+1] = l, r
+
+ l ^= expandedSalt[0]
+ r ^= expandedSalt[1]
+ l, r = encryptBlock(l, r, c)
+ c.s1[i+2], c.s1[i+3] = l, r
+ }
+
+ for i := 0; i < 256; i += 4 {
+ l ^= expandedSalt[2]
+ r ^= expandedSalt[3]
+ l, r = encryptBlock(l, r, c)
+ c.s2[i], c.s2[i+1] = l, r
+
+ l ^= expandedSalt[0]
+ r ^= expandedSalt[1]
+ l, r = encryptBlock(l, r, c)
+ c.s2[i+2], c.s2[i+3] = l, r
+ }
+
+ for i := 0; i < 256; i += 4 {
+ l ^= expandedSalt[2]
+ r ^= expandedSalt[3]
+ l, r = encryptBlock(l, r, c)
+ c.s3[i], c.s3[i+1] = l, r
+
+ l ^= expandedSalt[0]
+ r ^= expandedSalt[1]
+ l, r = encryptBlock(l, r, c)
+ c.s3[i+2], c.s3[i+3] = l, r
+ }
+}
+
+func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
+ xl, xr := l, r
+ xl ^= c.p[0]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16]
+ xr ^= c.p[17]
+ return xr, xl
+}
+
+func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
+ xl, xr := l, r
+ xl ^= c.p[17]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1]
+ xr ^= c.p[0]
+ return xr, xl
+}
+
+func zero(x []uint32) {
+ for i := range x {
+ x[i] = 0
+ }
+}
diff --git a/blowfish/blowfish_test.go b/blowfish/blowfish_test.go
new file mode 100644
index 0000000..1038d2e
--- /dev/null
+++ b/blowfish/blowfish_test.go
@@ -0,0 +1,210 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package blowfish
+
+import (
+ "testing"
+)
+
+type CryptTest struct {
+ key []byte
+ in []byte
+ out []byte
+}
+
+// Test vector values are from http://www.schneier.com/code/vectors.txt.
+var encryptTests = []CryptTest{
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+ {
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x51, 0x86, 0x6F, 0xD5, 0xB8, 0x5E, 0xCB, 0x8A}},
+ {
+ []byte{0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
+ []byte{0x7D, 0x85, 0x6F, 0x9A, 0x61, 0x30, 0x63, 0xF2}},
+ {
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x24, 0x66, 0xDD, 0x87, 0x8B, 0x96, 0x3C, 0x9D}},
+
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x61, 0xF9, 0xC3, 0x80, 0x22, 0x81, 0xB0, 0x96}},
+ {
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x7D, 0x0C, 0xC6, 0x30, 0xAF, 0xDA, 0x1E, 0xC7}},
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+ {
+ []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x0A, 0xCE, 0xAB, 0x0F, 0xC6, 0xA0, 0xA2, 0x8D}},
+ {
+ []byte{0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57},
+ []byte{0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42},
+ []byte{0x59, 0xC6, 0x82, 0x45, 0xEB, 0x05, 0x28, 0x2B}},
+ {
+ []byte{0x01, 0x31, 0xD9, 0x61, 0x9D, 0xC1, 0x37, 0x6E},
+ []byte{0x5C, 0xD5, 0x4C, 0xA8, 0x3D, 0xEF, 0x57, 0xDA},
+ []byte{0xB1, 0xB8, 0xCC, 0x0B, 0x25, 0x0F, 0x09, 0xA0}},
+ {
+ []byte{0x07, 0xA1, 0x13, 0x3E, 0x4A, 0x0B, 0x26, 0x86},
+ []byte{0x02, 0x48, 0xD4, 0x38, 0x06, 0xF6, 0x71, 0x72},
+ []byte{0x17, 0x30, 0xE5, 0x77, 0x8B, 0xEA, 0x1D, 0xA4}},
+ {
+ []byte{0x38, 0x49, 0x67, 0x4C, 0x26, 0x02, 0x31, 0x9E},
+ []byte{0x51, 0x45, 0x4B, 0x58, 0x2D, 0xDF, 0x44, 0x0A},
+ []byte{0xA2, 0x5E, 0x78, 0x56, 0xCF, 0x26, 0x51, 0xEB}},
+ {
+ []byte{0x04, 0xB9, 0x15, 0xBA, 0x43, 0xFE, 0xB5, 0xB6},
+ []byte{0x42, 0xFD, 0x44, 0x30, 0x59, 0x57, 0x7F, 0xA2},
+ []byte{0x35, 0x38, 0x82, 0xB1, 0x09, 0xCE, 0x8F, 0x1A}},
+ {
+ []byte{0x01, 0x13, 0xB9, 0x70, 0xFD, 0x34, 0xF2, 0xCE},
+ []byte{0x05, 0x9B, 0x5E, 0x08, 0x51, 0xCF, 0x14, 0x3A},
+ []byte{0x48, 0xF4, 0xD0, 0x88, 0x4C, 0x37, 0x99, 0x18}},
+ {
+ []byte{0x01, 0x70, 0xF1, 0x75, 0x46, 0x8F, 0xB5, 0xE6},
+ []byte{0x07, 0x56, 0xD8, 0xE0, 0x77, 0x47, 0x61, 0xD2},
+ []byte{0x43, 0x21, 0x93, 0xB7, 0x89, 0x51, 0xFC, 0x98}},
+ {
+ []byte{0x43, 0x29, 0x7F, 0xAD, 0x38, 0xE3, 0x73, 0xFE},
+ []byte{0x76, 0x25, 0x14, 0xB8, 0x29, 0xBF, 0x48, 0x6A},
+ []byte{0x13, 0xF0, 0x41, 0x54, 0xD6, 0x9D, 0x1A, 0xE5}},
+ {
+ []byte{0x07, 0xA7, 0x13, 0x70, 0x45, 0xDA, 0x2A, 0x16},
+ []byte{0x3B, 0xDD, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02},
+ []byte{0x2E, 0xED, 0xDA, 0x93, 0xFF, 0xD3, 0x9C, 0x79}},
+ {
+ []byte{0x04, 0x68, 0x91, 0x04, 0xC2, 0xFD, 0x3B, 0x2F},
+ []byte{0x26, 0x95, 0x5F, 0x68, 0x35, 0xAF, 0x60, 0x9A},
+ []byte{0xD8, 0x87, 0xE0, 0x39, 0x3C, 0x2D, 0xA6, 0xE3}},
+ {
+ []byte{0x37, 0xD0, 0x6B, 0xB5, 0x16, 0xCB, 0x75, 0x46},
+ []byte{0x16, 0x4D, 0x5E, 0x40, 0x4F, 0x27, 0x52, 0x32},
+ []byte{0x5F, 0x99, 0xD0, 0x4F, 0x5B, 0x16, 0x39, 0x69}},
+ {
+ []byte{0x1F, 0x08, 0x26, 0x0D, 0x1A, 0xC2, 0x46, 0x5E},
+ []byte{0x6B, 0x05, 0x6E, 0x18, 0x75, 0x9F, 0x5C, 0xCA},
+ []byte{0x4A, 0x05, 0x7A, 0x3B, 0x24, 0xD3, 0x97, 0x7B}},
+ {
+ []byte{0x58, 0x40, 0x23, 0x64, 0x1A, 0xBA, 0x61, 0x76},
+ []byte{0x00, 0x4B, 0xD6, 0xEF, 0x09, 0x17, 0x60, 0x62},
+ []byte{0x45, 0x20, 0x31, 0xC1, 0xE4, 0xFA, 0xDA, 0x8E}},
+ {
+ []byte{0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xB0, 0x07},
+ []byte{0x48, 0x0D, 0x39, 0x00, 0x6E, 0xE7, 0x62, 0xF2},
+ []byte{0x75, 0x55, 0xAE, 0x39, 0xF5, 0x9B, 0x87, 0xBD}},
+ {
+ []byte{0x49, 0x79, 0x3E, 0xBC, 0x79, 0xB3, 0x25, 0x8F},
+ []byte{0x43, 0x75, 0x40, 0xC8, 0x69, 0x8F, 0x3C, 0xFA},
+ []byte{0x53, 0xC5, 0x5F, 0x9C, 0xB4, 0x9F, 0xC0, 0x19}},
+ {
+ []byte{0x4F, 0xB0, 0x5E, 0x15, 0x15, 0xAB, 0x73, 0xA7},
+ []byte{0x07, 0x2D, 0x43, 0xA0, 0x77, 0x07, 0x52, 0x92},
+ []byte{0x7A, 0x8E, 0x7B, 0xFA, 0x93, 0x7E, 0x89, 0xA3}},
+ {
+ []byte{0x49, 0xE9, 0x5D, 0x6D, 0x4C, 0xA2, 0x29, 0xBF},
+ []byte{0x02, 0xFE, 0x55, 0x77, 0x81, 0x17, 0xF1, 0x2A},
+ []byte{0xCF, 0x9C, 0x5D, 0x7A, 0x49, 0x86, 0xAD, 0xB5}},
+ {
+ []byte{0x01, 0x83, 0x10, 0xDC, 0x40, 0x9B, 0x26, 0xD6},
+ []byte{0x1D, 0x9D, 0x5C, 0x50, 0x18, 0xF7, 0x28, 0xC2},
+ []byte{0xD1, 0xAB, 0xB2, 0x90, 0x65, 0x8B, 0xC7, 0x78}},
+ {
+ []byte{0x1C, 0x58, 0x7F, 0x1C, 0x13, 0x92, 0x4F, 0xEF},
+ []byte{0x30, 0x55, 0x32, 0x28, 0x6D, 0x6F, 0x29, 0x5A},
+ []byte{0x55, 0xCB, 0x37, 0x74, 0xD1, 0x3E, 0xF2, 0x01}},
+ {
+ []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xFA, 0x34, 0xEC, 0x48, 0x47, 0xB2, 0x68, 0xB2}},
+ {
+ []byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xA7, 0x90, 0x79, 0x51, 0x08, 0xEA, 0x3C, 0xAE}},
+ {
+ []byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xC3, 0x9E, 0x07, 0x2D, 0x9F, 0xAC, 0x63, 0x1D}},
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x01, 0x49, 0x33, 0xE0, 0xCD, 0xAF, 0xF6, 0xE4}},
+ {
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xF2, 0x1E, 0x9A, 0x77, 0xB7, 0x1C, 0x49, 0xBC}},
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x24, 0x59, 0x46, 0x88, 0x57, 0x54, 0x36, 0x9A}},
+ {
+ []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x6B, 0x5C, 0x5A, 0x9C, 0x5D, 0x9E, 0x0A, 0x5A}},
+}
+
+func TestCipherEncrypt(t *testing.T) {
+ for i, tt := range encryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+ continue
+ }
+ ct := make([]byte, len(tt.out))
+ c.Encrypt(ct, tt.in)
+ for j, v := range ct {
+ if v != tt.out[j] {
+ t.Errorf("Cipher.Encrypt, test vector #%d: cipher-text[%d] = %#x, expected %#x", i, j, v, tt.out[j])
+ break
+ }
+ }
+ }
+}
+
+func TestCipherDecrypt(t *testing.T) {
+ for i, tt := range encryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+ continue
+ }
+ pt := make([]byte, len(tt.in))
+ c.Decrypt(pt, tt.out)
+ for j, v := range pt {
+ if v != tt.in[j] {
+ t.Errorf("Cipher.Decrypt, test vector #%d: plain-text[%d] = %#x, expected %#x", i, j, v, tt.in[j])
+ break
+ }
+ }
+ }
+}
+
+func TestSaltedCipherKeyLength(t *testing.T) {
+ var key []byte
+ for i := 0; i < 4; i++ {
+ _, err := NewSaltedCipher(key, []byte{'a'})
+ if err != KeySizeError(i) {
+ t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(i))
+ }
+ key = append(key, 'a')
+ }
+
+ // A 57-byte key. One over the typical blowfish restriction.
+ key = []byte("012345678901234567890123456789012345678901234567890123456")
+ _, err := NewSaltedCipher(key, []byte{'a'})
+ if err != nil {
+ t.Errorf("NewSaltedCipher with long key, gave error %#v", err)
+ }
+}
diff --git a/blowfish/cipher.go b/blowfish/cipher.go
new file mode 100644
index 0000000..94e10f0
--- /dev/null
+++ b/blowfish/cipher.go
@@ -0,0 +1,100 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm.
+package blowfish
+
+// The code is a port of Bruce Schneier's C implementation.
+// See http://www.schneier.com/blowfish.html.
+
+import "strconv"
+
+// The Blowfish block size in bytes.
+const BlockSize = 8
+
+// A Cipher is an instance of Blowfish encryption using a particular key.
+type Cipher struct {
+ p [18]uint32
+ s0, s1, s2, s3 [256]uint32
+}
+
+type KeySizeError int
+
+func (k KeySizeError) Error() string {
+ return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k))
+}
+
+// NewCipher creates and returns a Cipher.
+// The key argument should be the Blowfish key, 4 to 56 bytes.
+func NewCipher(key []byte) (*Cipher, error) {
+ var result Cipher
+ k := len(key)
+ if k < 4 || k > 56 {
+ return nil, KeySizeError(k)
+ }
+ initCipher(key, &result)
+ ExpandKey(key, &result)
+ return &result, nil
+}
+
+// NewSaltedCipher creates a returns a Cipher that folds a salt into its key
+// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is
+// sufficient and desirable. For bcrypt compatiblity, the key can be over 56
+// bytes.
+func NewSaltedCipher(key, salt []byte) (*Cipher, error) {
+ var result Cipher
+ k := len(key)
+ if k < 4 {
+ return nil, KeySizeError(k)
+ }
+ initCipher(key, &result)
+ expandKeyWithSalt(key, salt, &result)
+ return &result, nil
+}
+
+// BlockSize returns the Blowfish block size, 8 bytes.
+// It is necessary to satisfy the Block interface in the
+// package "crypto/cipher".
+func (c *Cipher) BlockSize() int { return BlockSize }
+
+// Encrypt encrypts the 8-byte buffer src using the key k
+// and stores the result in dst.
+// Note that for amounts of data larger than a block,
+// it is not safe to just call Encrypt on successive blocks;
+// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
+func (c *Cipher) Encrypt(dst, src []byte) {
+ l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+ r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+ l, r = encryptBlock(l, r, c)
+ dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
+ dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
+}
+
+// Decrypt decrypts the 8-byte buffer src using the key k
+// and stores the result in dst.
+func (c *Cipher) Decrypt(dst, src []byte) {
+ l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+ r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+ l, r = decryptBlock(l, r, c)
+ dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
+ dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
+}
+
+// Reset zeros the key data, so that it will no longer
+// appear in the process's memory.
+func (c *Cipher) Reset() {
+ zero(c.p[0:])
+ zero(c.s0[0:])
+ zero(c.s1[0:])
+ zero(c.s2[0:])
+ zero(c.s3[0:])
+}
+
+func initCipher(key []byte, c *Cipher) {
+ copy(c.p[0:], p[0:])
+ copy(c.s0[0:], s0[0:])
+ copy(c.s1[0:], s1[0:])
+ copy(c.s2[0:], s2[0:])
+ copy(c.s3[0:], s3[0:])
+}
diff --git a/blowfish/const.go b/blowfish/const.go
new file mode 100644
index 0000000..8c5ee4c
--- /dev/null
+++ b/blowfish/const.go
@@ -0,0 +1,199 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The startup permutation array and substitution boxes.
+// They are the hexadecimal digits of PI; see:
+// http://www.schneier.com/code/constants.txt.
+
+package blowfish
+
+var s0 = [256]uint32{
+ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
+ 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+ 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
+ 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+ 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,
+ 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+ 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
+ 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+ 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
+ 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+ 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,
+ 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+ 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
+ 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+ 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
+ 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+ 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,
+ 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+ 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
+ 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+ 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
+ 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+ 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,
+ 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+ 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
+ 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+ 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
+ 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+ 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,
+ 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+ 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
+ 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+ 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
+ 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+ 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,
+ 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+ 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
+ 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+ 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
+ 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+ 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,
+ 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+ 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
+}
+
+var s1 = [256]uint32{
+ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,
+ 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+ 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,
+ 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+ 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,
+ 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+ 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
+ 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+ 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,
+ 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+ 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,
+ 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+ 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,
+ 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+ 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,
+ 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+ 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
+ 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+ 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,
+ 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+ 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,
+ 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+ 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,
+ 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+ 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,
+ 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+ 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
+ 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+ 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,
+ 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+ 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,
+ 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+ 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,
+ 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+ 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,
+ 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+ 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
+ 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+ 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,
+ 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+ 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,
+ 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+ 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
+}
+
+var s2 = [256]uint32{
+ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,
+ 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+ 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,
+ 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+ 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,
+ 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+ 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,
+ 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+ 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,
+ 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+ 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,
+ 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+ 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,
+ 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+ 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,
+ 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+ 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,
+ 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+ 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,
+ 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+ 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,
+ 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+ 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,
+ 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+ 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,
+ 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+ 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,
+ 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+ 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,
+ 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+ 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,
+ 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+ 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,
+ 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+ 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,
+ 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+ 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,
+ 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+ 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,
+ 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+ 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,
+ 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+ 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
+}
+
+var s3 = [256]uint32{
+ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
+ 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+ 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,
+ 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+ 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
+ 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+ 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
+ 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+ 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,
+ 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+ 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
+ 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+ 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
+ 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+ 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,
+ 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+ 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
+ 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+ 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
+ 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+ 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,
+ 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+ 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
+ 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+ 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
+ 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+ 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,
+ 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+ 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
+ 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+ 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
+ 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+ 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,
+ 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+ 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
+ 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+ 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
+ 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+ 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,
+ 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+ 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
+ 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+ 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
+}
+
+var p = [18]uint32{
+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
+ 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+ 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b,
+}
diff --git a/cast5/cast5.go b/cast5/cast5.go
new file mode 100644
index 0000000..889a13c
--- /dev/null
+++ b/cast5/cast5.go
@@ -0,0 +1,534 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cast5 implements CAST5, as defined in RFC 2144. CAST5 is a common
+// OpenPGP cipher.
+package cast5
+
+import "errors"
+
+const BlockSize = 8
+const KeySize = 16
+
+type Cipher struct {
+ masking [16]uint32
+ rotate [16]uint8
+}
+
+func NewCipher(key []byte) (c *Cipher, err error) {
+ if len(key) != KeySize {
+ return nil, errors.New("CAST5: keys must be 16 bytes")
+ }
+
+ c = new(Cipher)
+ c.keySchedule(key)
+ return
+}
+
+func (c *Cipher) BlockSize() int {
+ return BlockSize
+}
+
+// Reset zeros the key material in memory.
+func (c *Cipher) Reset() {
+ for i := 0; i < 16; i++ {
+ c.masking[i] = 0
+ c.rotate[i] = 0
+ }
+}
+
+func (c *Cipher) Encrypt(dst, src []byte) {
+ l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+ r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+
+ l, r = r, l^f1(r, c.masking[0], c.rotate[0])
+ l, r = r, l^f2(r, c.masking[1], c.rotate[1])
+ l, r = r, l^f3(r, c.masking[2], c.rotate[2])
+ l, r = r, l^f1(r, c.masking[3], c.rotate[3])
+
+ l, r = r, l^f2(r, c.masking[4], c.rotate[4])
+ l, r = r, l^f3(r, c.masking[5], c.rotate[5])
+ l, r = r, l^f1(r, c.masking[6], c.rotate[6])
+ l, r = r, l^f2(r, c.masking[7], c.rotate[7])
+
+ l, r = r, l^f3(r, c.masking[8], c.rotate[8])
+ l, r = r, l^f1(r, c.masking[9], c.rotate[9])
+ l, r = r, l^f2(r, c.masking[10], c.rotate[10])
+ l, r = r, l^f3(r, c.masking[11], c.rotate[11])
+
+ l, r = r, l^f1(r, c.masking[12], c.rotate[12])
+ l, r = r, l^f2(r, c.masking[13], c.rotate[13])
+ l, r = r, l^f3(r, c.masking[14], c.rotate[14])
+ l, r = r, l^f1(r, c.masking[15], c.rotate[15])
+
+ dst[0] = uint8(r >> 24)
+ dst[1] = uint8(r >> 16)
+ dst[2] = uint8(r >> 8)
+ dst[3] = uint8(r)
+ dst[4] = uint8(l >> 24)
+ dst[5] = uint8(l >> 16)
+ dst[6] = uint8(l >> 8)
+ dst[7] = uint8(l)
+}
+
+func (c *Cipher) Decrypt(dst, src []byte) {
+ l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+ r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+
+ l, r = r, l^f1(r, c.masking[15], c.rotate[15])
+ l, r = r, l^f3(r, c.masking[14], c.rotate[14])
+ l, r = r, l^f2(r, c.masking[13], c.rotate[13])
+ l, r = r, l^f1(r, c.masking[12], c.rotate[12])
+
+ l, r = r, l^f3(r, c.masking[11], c.rotate[11])
+ l, r = r, l^f2(r, c.masking[10], c.rotate[10])
+ l, r = r, l^f1(r, c.masking[9], c.rotate[9])
+ l, r = r, l^f3(r, c.masking[8], c.rotate[8])
+
+ l, r = r, l^f2(r, c.masking[7], c.rotate[7])
+ l, r = r, l^f1(r, c.masking[6], c.rotate[6])
+ l, r = r, l^f3(r, c.masking[5], c.rotate[5])
+ l, r = r, l^f2(r, c.masking[4], c.rotate[4])
+
+ l, r = r, l^f1(r, c.masking[3], c.rotate[3])
+ l, r = r, l^f3(r, c.masking[2], c.rotate[2])
+ l, r = r, l^f2(r, c.masking[1], c.rotate[1])
+ l, r = r, l^f1(r, c.masking[0], c.rotate[0])
+
+ dst[0] = uint8(r >> 24)
+ dst[1] = uint8(r >> 16)
+ dst[2] = uint8(r >> 8)
+ dst[3] = uint8(r)
+ dst[4] = uint8(l >> 24)
+ dst[5] = uint8(l >> 16)
+ dst[6] = uint8(l >> 8)
+ dst[7] = uint8(l)
+}
+
+type keyScheduleA [4][7]uint8
+type keyScheduleB [4][5]uint8
+
+// keyScheduleRound contains the magic values for a round of the key schedule.
+// The keyScheduleA deals with the lines like:
+// z0z1z2z3 = x0x1x2x3 ^ S5[xD] ^ S6[xF] ^ S7[xC] ^ S8[xE] ^ S7[x8]
+// Conceptually, both x and z are in the same array, x first. The first
+// element describes which word of this array gets written to and the
+// second, which word gets read. So, for the line above, it's "4, 0", because
+// it's writing to the first word of z, which, being after x, is word 4, and
+// reading from the first word of x: word 0.
+//
+// Next are the indexes into the S-boxes. Now the array is treated as bytes. So
+// "xD" is 0xd. The first byte of z is written as "16 + 0", just to be clear
+// that it's z that we're indexing.
+//
+// keyScheduleB deals with lines like:
+// K1 = S5[z8] ^ S6[z9] ^ S7[z7] ^ S8[z6] ^ S5[z2]
+// "K1" is ignored because key words are always written in order. So the five
+// elements are the S-box indexes. They use the same form as in keyScheduleA,
+// above.
+
+type keyScheduleRound struct{}
+type keySchedule []keyScheduleRound
+
+var schedule = []struct {
+ a keyScheduleA
+ b keyScheduleB
+}{
+ {
+ keyScheduleA{
+ {4, 0, 0xd, 0xf, 0xc, 0xe, 0x8},
+ {5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
+ {6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
+ {7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
+ },
+ keyScheduleB{
+ {16 + 8, 16 + 9, 16 + 7, 16 + 6, 16 + 2},
+ {16 + 0xa, 16 + 0xb, 16 + 5, 16 + 4, 16 + 6},
+ {16 + 0xc, 16 + 0xd, 16 + 3, 16 + 2, 16 + 9},
+ {16 + 0xe, 16 + 0xf, 16 + 1, 16 + 0, 16 + 0xc},
+ },
+ },
+ {
+ keyScheduleA{
+ {0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
+ {1, 4, 0, 2, 1, 3, 16 + 2},
+ {2, 5, 7, 6, 5, 4, 16 + 1},
+ {3, 7, 0xa, 9, 0xb, 8, 16 + 3},
+ },
+ keyScheduleB{
+ {3, 2, 0xc, 0xd, 8},
+ {1, 0, 0xe, 0xf, 0xd},
+ {7, 6, 8, 9, 3},
+ {5, 4, 0xa, 0xb, 7},
+ },
+ },
+ {
+ keyScheduleA{
+ {4, 0, 0xd, 0xf, 0xc, 0xe, 8},
+ {5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
+ {6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
+ {7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
+ },
+ keyScheduleB{
+ {16 + 3, 16 + 2, 16 + 0xc, 16 + 0xd, 16 + 9},
+ {16 + 1, 16 + 0, 16 + 0xe, 16 + 0xf, 16 + 0xc},
+ {16 + 7, 16 + 6, 16 + 8, 16 + 9, 16 + 2},
+ {16 + 5, 16 + 4, 16 + 0xa, 16 + 0xb, 16 + 6},
+ },
+ },
+ {
+ keyScheduleA{
+ {0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
+ {1, 4, 0, 2, 1, 3, 16 + 2},
+ {2, 5, 7, 6, 5, 4, 16 + 1},
+ {3, 7, 0xa, 9, 0xb, 8, 16 + 3},
+ },
+ keyScheduleB{
+ {8, 9, 7, 6, 3},
+ {0xa, 0xb, 5, 4, 7},
+ {0xc, 0xd, 3, 2, 8},
+ {0xe, 0xf, 1, 0, 0xd},
+ },
+ },
+}
+
+func (c *Cipher) keySchedule(in []byte) {
+ var t [8]uint32
+ var k [32]uint32
+
+ for i := 0; i < 4; i++ {
+ j := i * 4
+ t[i] = uint32(in[j])<<24 | uint32(in[j+1])<<16 | uint32(in[j+2])<<8 | uint32(in[j+3])
+ }
+
+ x := []byte{6, 7, 4, 5}
+ ki := 0
+
+ for half := 0; half < 2; half++ {
+ for _, round := range schedule {
+ for j := 0; j < 4; j++ {
+ var a [7]uint8
+ copy(a[:], round.a[j][:])
+ w := t[a[1]]
+ w ^= sBox[4][(t[a[2]>>2]>>(24-8*(a[2]&3)))&0xff]
+ w ^= sBox[5][(t[a[3]>>2]>>(24-8*(a[3]&3)))&0xff]
+ w ^= sBox[6][(t[a[4]>>2]>>(24-8*(a[4]&3)))&0xff]
+ w ^= sBox[7][(t[a[5]>>2]>>(24-8*(a[5]&3)))&0xff]
+ w ^= sBox[x[j]][(t[a[6]>>2]>>(24-8*(a[6]&3)))&0xff]
+ t[a[0]] = w
+ }
+
+ for j := 0; j < 4; j++ {
+ var b [5]uint8
+ copy(b[:], round.b[j][:])
+ w := sBox[4][(t[b[0]>>2]>>(24-8*(b[0]&3)))&0xff]
+ w ^= sBox[5][(t[b[1]>>2]>>(24-8*(b[1]&3)))&0xff]
+ w ^= sBox[6][(t[b[2]>>2]>>(24-8*(b[2]&3)))&0xff]
+ w ^= sBox[7][(t[b[3]>>2]>>(24-8*(b[3]&3)))&0xff]
+ w ^= sBox[4+j][(t[b[4]>>2]>>(24-8*(b[4]&3)))&0xff]
+ k[ki] = w
+ ki++
+ }
+ }
+ }
+
+ for i := 0; i < 16; i++ {
+ c.masking[i] = k[i]
+ c.rotate[i] = uint8(k[16+i] & 0x1f)
+ }
+}
+
+// These are the three 'f' functions. See RFC 2144, section 2.2.
+func f1(d, m uint32, r uint8) uint32 {
+ t := m + d
+ I := (t << r) | (t >> (32 - r))
+ return ((sBox[0][I>>24] ^ sBox[1][(I>>16)&0xff]) - sBox[2][(I>>8)&0xff]) + sBox[3][I&0xff]
+}
+
+func f2(d, m uint32, r uint8) uint32 {
+ t := m ^ d
+ I := (t << r) | (t >> (32 - r))
+ return ((sBox[0][I>>24] - sBox[1][(I>>16)&0xff]) + sBox[2][(I>>8)&0xff]) ^ sBox[3][I&0xff]
+}
+
+func f3(d, m uint32, r uint8) uint32 {
+ t := m - d
+ I := (t << r) | (t >> (32 - r))
+ return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff]
+}
+
+var sBox = [8][256]uint32{
+ {
+ 0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949,
+ 0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e,
+ 0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d,
+ 0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0,
+ 0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7,
+ 0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935,
+ 0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d,
+ 0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50,
+ 0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe,
+ 0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3,
+ 0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167,
+ 0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291,
+ 0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779,
+ 0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2,
+ 0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511,
+ 0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d,
+ 0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5,
+ 0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324,
+ 0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c,
+ 0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc,
+ 0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d,
+ 0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96,
+ 0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a,
+ 0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d,
+ 0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd,
+ 0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6,
+ 0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9,
+ 0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872,
+ 0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c,
+ 0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e,
+ 0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9,
+ 0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf,
+ },
+ {
+ 0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651,
+ 0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3,
+ 0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb,
+ 0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806,
+ 0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b,
+ 0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359,
+ 0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b,
+ 0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c,
+ 0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34,
+ 0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb,
+ 0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd,
+ 0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860,
+ 0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b,
+ 0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304,
+ 0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b,
+ 0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf,
+ 0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c,
+ 0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13,
+ 0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f,
+ 0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6,
+ 0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6,
+ 0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58,
+ 0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906,
+ 0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d,
+ 0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6,
+ 0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4,
+ 0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6,
+ 0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f,
+ 0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249,
+ 0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa,
+ 0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9,
+ 0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1,
+ },
+ {
+ 0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90,
+ 0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5,
+ 0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e,
+ 0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240,
+ 0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5,
+ 0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b,
+ 0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71,
+ 0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04,
+ 0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82,
+ 0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15,
+ 0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2,
+ 0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176,
+ 0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148,
+ 0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc,
+ 0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341,
+ 0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e,
+ 0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51,
+ 0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f,
+ 0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a,
+ 0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b,
+ 0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b,
+ 0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5,
+ 0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45,
+ 0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536,
+ 0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc,
+ 0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0,
+ 0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69,
+ 0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2,
+ 0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49,
+ 0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d,
+ 0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a,
+ 0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783,
+ },
+ {
+ 0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1,
+ 0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf,
+ 0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15,
+ 0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121,
+ 0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25,
+ 0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5,
+ 0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb,
+ 0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5,
+ 0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d,
+ 0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6,
+ 0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23,
+ 0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003,
+ 0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6,
+ 0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119,
+ 0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24,
+ 0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a,
+ 0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79,
+ 0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df,
+ 0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26,
+ 0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab,
+ 0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7,
+ 0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417,
+ 0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2,
+ 0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2,
+ 0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a,
+ 0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919,
+ 0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef,
+ 0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876,
+ 0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab,
+ 0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04,
+ 0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282,
+ 0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2,
+ },
+ {
+ 0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f,
+ 0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a,
+ 0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff,
+ 0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02,
+ 0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a,
+ 0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7,
+ 0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9,
+ 0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981,
+ 0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774,
+ 0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655,
+ 0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2,
+ 0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910,
+ 0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1,
+ 0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da,
+ 0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049,
+ 0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f,
+ 0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba,
+ 0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be,
+ 0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3,
+ 0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840,
+ 0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4,
+ 0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2,
+ 0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7,
+ 0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5,
+ 0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e,
+ 0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e,
+ 0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801,
+ 0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad,
+ 0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0,
+ 0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20,
+ 0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8,
+ 0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4,
+ },
+ {
+ 0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac,
+ 0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138,
+ 0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367,
+ 0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98,
+ 0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072,
+ 0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3,
+ 0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd,
+ 0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8,
+ 0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9,
+ 0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54,
+ 0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387,
+ 0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc,
+ 0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf,
+ 0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf,
+ 0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f,
+ 0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289,
+ 0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950,
+ 0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f,
+ 0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b,
+ 0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be,
+ 0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13,
+ 0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976,
+ 0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0,
+ 0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891,
+ 0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da,
+ 0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc,
+ 0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084,
+ 0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25,
+ 0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121,
+ 0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5,
+ 0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd,
+ 0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f,
+ },
+ {
+ 0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f,
+ 0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de,
+ 0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43,
+ 0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19,
+ 0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2,
+ 0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516,
+ 0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88,
+ 0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816,
+ 0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756,
+ 0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a,
+ 0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264,
+ 0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688,
+ 0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28,
+ 0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3,
+ 0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7,
+ 0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06,
+ 0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033,
+ 0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a,
+ 0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566,
+ 0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509,
+ 0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962,
+ 0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e,
+ 0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c,
+ 0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c,
+ 0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285,
+ 0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301,
+ 0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be,
+ 0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767,
+ 0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647,
+ 0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914,
+ 0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c,
+ 0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3,
+ },
+ {
+ 0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5,
+ 0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc,
+ 0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd,
+ 0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d,
+ 0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2,
+ 0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862,
+ 0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc,
+ 0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c,
+ 0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e,
+ 0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039,
+ 0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8,
+ 0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42,
+ 0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5,
+ 0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472,
+ 0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225,
+ 0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c,
+ 0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb,
+ 0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054,
+ 0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70,
+ 0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc,
+ 0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c,
+ 0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3,
+ 0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4,
+ 0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101,
+ 0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f,
+ 0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e,
+ 0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a,
+ 0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c,
+ 0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384,
+ 0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c,
+ 0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82,
+ 0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e,
+ },
+}
diff --git a/cast5/cast5_test.go b/cast5/cast5_test.go
new file mode 100644
index 0000000..5f7025f
--- /dev/null
+++ b/cast5/cast5_test.go
@@ -0,0 +1,104 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cast5
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+)
+
+// This test vector is taken from RFC 2144, App B.1.
+// Since the other two test vectors are for reduced-round variants, we can't
+// use them.
+var basicTests = []struct {
+ key, plainText, cipherText string
+}{
+ {
+ "0123456712345678234567893456789a",
+ "0123456789abcdef",
+ "238b4fe5847e44b2",
+ },
+}
+
+func TestBasic(t *testing.T) {
+ for i, test := range basicTests {
+ key, _ := hex.DecodeString(test.key)
+ plainText, _ := hex.DecodeString(test.plainText)
+ expected, _ := hex.DecodeString(test.cipherText)
+
+ c, err := NewCipher(key)
+ if err != nil {
+ t.Errorf("#%d: failed to create Cipher: %s", i, err)
+ continue
+ }
+ var cipherText [BlockSize]byte
+ c.Encrypt(cipherText[:], plainText)
+ if !bytes.Equal(cipherText[:], expected) {
+ t.Errorf("#%d: got:%x want:%x", i, cipherText, expected)
+ }
+
+ var plainTextAgain [BlockSize]byte
+ c.Decrypt(plainTextAgain[:], cipherText[:])
+ if !bytes.Equal(plainTextAgain[:], plainText) {
+ t.Errorf("#%d: got:%x want:%x", i, plainTextAgain, plainText)
+ }
+ }
+}
+
+// TestFull performs the test specified in RFC 2144, App B.2.
+// However, due to the length of time taken, it's disabled here and a more
+// limited version is included, below.
+func TestFull(t *testing.T) {
+ // This is too slow for normal testing
+ return
+
+ a, b := iterate(1000000)
+
+ const expectedA = "eea9d0a249fd3ba6b3436fb89d6dca92"
+ const expectedB = "b2c95eb00c31ad7180ac05b8e83d696e"
+
+ if hex.EncodeToString(a) != expectedA {
+ t.Errorf("a: got:%x want:%s", a, expectedA)
+ }
+ if hex.EncodeToString(b) != expectedB {
+ t.Errorf("b: got:%x want:%s", b, expectedB)
+ }
+}
+
+func iterate(iterations int) ([]byte, []byte) {
+ const initValueHex = "0123456712345678234567893456789a"
+
+ initValue, _ := hex.DecodeString(initValueHex)
+
+ var a, b [16]byte
+ copy(a[:], initValue)
+ copy(b[:], initValue)
+
+ for i := 0; i < iterations; i++ {
+ c, _ := NewCipher(b[:])
+ c.Encrypt(a[:8], a[:8])
+ c.Encrypt(a[8:], a[8:])
+ c, _ = NewCipher(a[:])
+ c.Encrypt(b[:8], b[:8])
+ c.Encrypt(b[8:], b[8:])
+ }
+
+ return a[:], b[:]
+}
+
+func TestLimited(t *testing.T) {
+ a, b := iterate(1000)
+
+ const expectedA = "23f73b14b02a2ad7dfb9f2c35644798d"
+ const expectedB = "e5bf37eff14c456a40b21ce369370a9f"
+
+ if hex.EncodeToString(a) != expectedA {
+ t.Errorf("a: got:%x want:%s", a, expectedA)
+ }
+ if hex.EncodeToString(b) != expectedB {
+ t.Errorf("b: got:%x want:%s", b, expectedB)
+ }
+}
diff --git a/md4/md4.go b/md4/md4.go
new file mode 100644
index 0000000..c5f7c57
--- /dev/null
+++ b/md4/md4.go
@@ -0,0 +1,118 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
+package md4
+
+import (
+ "crypto"
+ "hash"
+)
+
+func init() {
+ crypto.RegisterHash(crypto.MD4, New)
+}
+
+// The size of an MD4 checksum in bytes.
+const Size = 16
+
+// The blocksize of MD4 in bytes.
+const BlockSize = 64
+
+const (
+ _Chunk = 64
+ _Init0 = 0x67452301
+ _Init1 = 0xEFCDAB89
+ _Init2 = 0x98BADCFE
+ _Init3 = 0x10325476
+)
+
+// digest represents the partial evaluation of a checksum.
+type digest struct {
+ s [4]uint32
+ x [_Chunk]byte
+ nx int
+ len uint64
+}
+
+func (d *digest) Reset() {
+ d.s[0] = _Init0
+ d.s[1] = _Init1
+ d.s[2] = _Init2
+ d.s[3] = _Init3
+ d.nx = 0
+ d.len = 0
+}
+
+// New returns a new hash.Hash computing the MD4 checksum.
+func New() hash.Hash {
+ d := new(digest)
+ d.Reset()
+ return d
+}
+
+func (d *digest) Size() int { return Size }
+
+func (d *digest) BlockSize() int { return BlockSize }
+
+func (d *digest) Write(p []byte) (nn int, err error) {
+ nn = len(p)
+ d.len += uint64(nn)
+ if d.nx > 0 {
+ n := len(p)
+ if n > _Chunk-d.nx {
+ n = _Chunk - d.nx
+ }
+ for i := 0; i < n; i++ {
+ d.x[d.nx+i] = p[i]
+ }
+ d.nx += n
+ if d.nx == _Chunk {
+ _Block(d, d.x[0:])
+ d.nx = 0
+ }
+ p = p[n:]
+ }
+ n := _Block(d, p)
+ p = p[n:]
+ if len(p) > 0 {
+ d.nx = copy(d.x[:], p)
+ }
+ return
+}
+
+func (d0 *digest) Sum(in []byte) []byte {
+ // Make a copy of d0, so that caller can keep writing and summing.
+ d := new(digest)
+ *d = *d0
+
+ // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
+ len := d.len
+ var tmp [64]byte
+ tmp[0] = 0x80
+ if len%64 < 56 {
+ d.Write(tmp[0 : 56-len%64])
+ } else {
+ d.Write(tmp[0 : 64+56-len%64])
+ }
+
+ // Length in bits.
+ len <<= 3
+ for i := uint(0); i < 8; i++ {
+ tmp[i] = byte(len >> (8 * i))
+ }
+ d.Write(tmp[0:8])
+
+ if d.nx != 0 {
+ panic("d.nx != 0")
+ }
+
+ for _, s := range d.s {
+ in = append(in, byte(s>>0))
+ in = append(in, byte(s>>8))
+ in = append(in, byte(s>>16))
+ in = append(in, byte(s>>24))
+ }
+ return in
+}
diff --git a/md4/md4_test.go b/md4/md4_test.go
new file mode 100644
index 0000000..b56edd7
--- /dev/null
+++ b/md4/md4_test.go
@@ -0,0 +1,71 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package md4
+
+import (
+ "fmt"
+ "io"
+ "testing"
+)
+
+type md4Test struct {
+ out string
+ in string
+}
+
+var golden = []md4Test{
+ {"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
+ {"bde52cb31de33e46245e05fbdbd6fb24", "a"},
+ {"ec388dd78999dfc7cf4632465693b6bf", "ab"},
+ {"a448017aaf21d8525fc10ae87aa6729d", "abc"},
+ {"41decd8f579255c5200f86a4bb3ba740", "abcd"},
+ {"9803f4a34e8eb14f96adba49064a0c41", "abcde"},
+ {"804e7f1c2586e50b49ac65db5b645131", "abcdef"},
+ {"752f4adfe53d1da0241b5bc216d098fc", "abcdefg"},
+ {"ad9daf8d49d81988590a6f0e745d15dd", "abcdefgh"},
+ {"1e4e28b05464316b56402b3815ed2dfd", "abcdefghi"},
+ {"dc959c6f5d6f9e04e4380777cc964b3d", "abcdefghij"},
+ {"1b5701e265778898ef7de5623bbe7cc0", "Discard medicine more than two years old."},
+ {"d7f087e090fe7ad4a01cb59dacc9a572", "He who has a shady past knows that nice guys finish last."},
+ {"a6f8fd6df617c72837592fc3570595c9", "I wouldn't marry him with a ten foot pole."},
+ {"c92a84a9526da8abc240c05d6b1a1ce0", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
+ {"f6013160c4dcb00847069fee3bb09803", "The days of the digital watch are numbered. -Tom Stoppard"},
+ {"2c3bb64f50b9107ed57640fe94bec09f", "Nepal premier won't resign."},
+ {"45b7d8a32c7806f2f7f897332774d6e4", "For every action there is an equal and opposite government program."},
+ {"b5b4f9026b175c62d7654bdc3a1cd438", "His money is twice tainted: 'taint yours and 'taint mine."},
+ {"caf44e80f2c20ce19b5ba1cab766e7bd", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
+ {"191fae6707f496aa54a6bce9f2ecf74d", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
+ {"9ddc753e7a4ccee6081cd1b45b23a834", "size: a.out: bad magic"},
+ {"8d050f55b1cadb9323474564be08a521", "The major problem is with sendmail. -Mark Horton"},
+ {"ad6e2587f74c3e3cc19146f6127fa2e3", "Give me a rock, paper and scissors and I will move the world. CCFestoon"},
+ {"1d616d60a5fabe85589c3f1566ca7fca", "If the enemy is within range, then so are you."},
+ {"aec3326a4f496a2ced65a1963f84577f", "It's well we cannot hear the screams/That we create in others' dreams."},
+ {"77b4fd762d6b9245e61c50bf6ebf118b", "You remind me of a TV show, but that's all right: I watch it anyway."},
+ {"e8f48c726bae5e516f6ddb1a4fe62438", "C is as portable as Stonehedge!!"},
+ {"a3a84366e7219e887423b01f9be7166e", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
+ {"a6b7aa35157e984ef5d9b7f32e5fbb52", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"},
+ {"75661f0545955f8f9abeeb17845f3fd6", "How can you write a big system without C++? -Paul Glick"},
+}
+
+func TestGolden(t *testing.T) {
+ for i := 0; i < len(golden); i++ {
+ g := golden[i]
+ c := New()
+ for j := 0; j < 3; j++ {
+ if j < 2 {
+ io.WriteString(c, g.in)
+ } else {
+ io.WriteString(c, g.in[0:len(g.in)/2])
+ c.Sum(nil)
+ io.WriteString(c, g.in[len(g.in)/2:])
+ }
+ s := fmt.Sprintf("%x", c.Sum(nil))
+ if s != g.out {
+ t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out)
+ }
+ c.Reset()
+ }
+ }
+}
diff --git a/md4/md4block.go b/md4/md4block.go
new file mode 100644
index 0000000..3fed475
--- /dev/null
+++ b/md4/md4block.go
@@ -0,0 +1,89 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// MD4 block step.
+// In its own file so that a faster assembly or C version
+// can be substituted easily.
+
+package md4
+
+var shift1 = []uint{3, 7, 11, 19}
+var shift2 = []uint{3, 5, 9, 13}
+var shift3 = []uint{3, 9, 11, 15}
+
+var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}
+var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
+
+func _Block(dig *digest, p []byte) int {
+ a := dig.s[0]
+ b := dig.s[1]
+ c := dig.s[2]
+ d := dig.s[3]
+ n := 0
+ var X [16]uint32
+ for len(p) >= _Chunk {
+ aa, bb, cc, dd := a, b, c, d
+
+ j := 0
+ for i := 0; i < 16; i++ {
+ X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
+ j += 4
+ }
+
+ // If this needs to be made faster in the future,
+ // the usual trick is to unroll each of these
+ // loops by a factor of 4; that lets you replace
+ // the shift[] lookups with constants and,
+ // with suitable variable renaming in each
+ // unrolled body, delete the a, b, c, d = d, a, b, c
+ // (or you can let the optimizer do the renaming).
+ //
+ // The index variables are uint so that % by a power
+ // of two can be optimized easily by a compiler.
+
+ // Round 1.
+ for i := uint(0); i < 16; i++ {
+ x := i
+ s := shift1[i%4]
+ f := ((c ^ d) & b) ^ d
+ a += f + X[x]
+ a = a<<s | a>>(32-s)
+ a, b, c, d = d, a, b, c
+ }
+
+ // Round 2.
+ for i := uint(0); i < 16; i++ {
+ x := xIndex2[i]
+ s := shift2[i%4]
+ g := (b & c) | (b & d) | (c & d)
+ a += g + X[x] + 0x5a827999
+ a = a<<s | a>>(32-s)
+ a, b, c, d = d, a, b, c
+ }
+
+ // Round 3.
+ for i := uint(0); i < 16; i++ {
+ x := xIndex3[i]
+ s := shift3[i%4]
+ h := b ^ c ^ d
+ a += h + X[x] + 0x6ed9eba1
+ a = a<<s | a>>(32-s)
+ a, b, c, d = d, a, b, c
+ }
+
+ a += aa
+ b += bb
+ c += cc
+ d += dd
+
+ p = p[_Chunk:]
+ n += _Chunk
+ }
+
+ dig.s[0] = a
+ dig.s[1] = b
+ dig.s[2] = c
+ dig.s[3] = d
+ return n
+}
diff --git a/ocsp/ocsp.go b/ocsp/ocsp.go
new file mode 100644
index 0000000..b9dfdf9
--- /dev/null
+++ b/ocsp/ocsp.go
@@ -0,0 +1,191 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses
+// are signed messages attesting to the validity of a certificate for a small
+// period of time. This is used to manage revocation for X.509 certificates.
+package ocsp
+
+import (
+ "crypto"
+ "crypto/rsa"
+ _ "crypto/sha1"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "time"
+)
+
+var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
+var idSHA1WithRSA = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 1, 5})
+
+// These are internal structures that reflect the ASN.1 structure of an OCSP
+// response. See RFC 2560, section 4.2.
+
+const (
+ ocspSuccess = 0
+ ocspMalformed = 1
+ ocspInternalError = 2
+ ocspTryLater = 3
+ ocspSigRequired = 4
+ ocspUnauthorized = 5
+)
+
+type certID struct {
+ HashAlgorithm pkix.AlgorithmIdentifier
+ NameHash []byte
+ IssuerKeyHash []byte
+ SerialNumber asn1.RawValue
+}
+
+type responseASN1 struct {
+ Status asn1.Enumerated
+ Response responseBytes `asn1:"explicit,tag:0"`
+}
+
+type responseBytes struct {
+ ResponseType asn1.ObjectIdentifier
+ Response []byte
+}
+
+type basicResponse struct {
+ TBSResponseData responseData
+ SignatureAlgorithm pkix.AlgorithmIdentifier
+ Signature asn1.BitString
+ Certificates []asn1.RawValue `asn1:"explicit,tag:0,optional"`
+}
+
+type responseData struct {
+ Raw asn1.RawContent
+ Version int `asn1:"optional,default:1,explicit,tag:0"`
+ RequestorName pkix.RDNSequence `asn1:"optional,explicit,tag:1"`
+ KeyHash []byte `asn1:"optional,explicit,tag:2"`
+ ProducedAt time.Time
+ Responses []singleResponse
+}
+
+type singleResponse struct {
+ CertID certID
+ Good asn1.Flag `asn1:"explicit,tag:0,optional"`
+ Revoked revokedInfo `asn1:"explicit,tag:1,optional"`
+ Unknown asn1.Flag `asn1:"explicit,tag:2,optional"`
+ ThisUpdate time.Time
+ NextUpdate time.Time `asn1:"explicit,tag:0,optional"`
+}
+
+type revokedInfo struct {
+ RevocationTime time.Time
+ Reason int `asn1:"explicit,tag:0,optional"`
+}
+
+// This is the exposed reflection of the internal OCSP structures.
+
+const (
+ // Good means that the certificate is valid.
+ Good = iota
+ // Revoked means that the certificate has been deliberately revoked.
+ Revoked = iota
+ // Unknown means that the OCSP responder doesn't know about the certificate.
+ Unknown = iota
+ // ServerFailed means that the OCSP responder failed to process the request.
+ ServerFailed = iota
+)
+
+// Response represents an OCSP response. See RFC 2560.
+type Response struct {
+ // Status is one of {Good, Revoked, Unknown, ServerFailed}
+ Status int
+ SerialNumber []byte
+ ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
+ RevocationReason int
+ Certificate *x509.Certificate
+}
+
+// ParseError results from an invalid OCSP response.
+type ParseError string
+
+func (p ParseError) Error() string {
+ return string(p)
+}
+
+// ParseResponse parses an OCSP response in DER form. It only supports
+// responses for a single certificate and only those using RSA signatures.
+// Non-RSA responses will result in an x509.UnsupportedAlgorithmError.
+// Signature errors or parse failures will result in a ParseError.
+func ParseResponse(bytes []byte) (*Response, error) {
+ var resp responseASN1
+ rest, err := asn1.Unmarshal(bytes, &resp)
+ if err != nil {
+ return nil, err
+ }
+ if len(rest) > 0 {
+ return nil, ParseError("trailing data in OCSP response")
+ }
+
+ ret := new(Response)
+ if resp.Status != ocspSuccess {
+ ret.Status = ServerFailed
+ return ret, nil
+ }
+
+ if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
+ return nil, ParseError("bad OCSP response type")
+ }
+
+ var basicResp basicResponse
+ rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(basicResp.Certificates) != 1 {
+ return nil, ParseError("OCSP response contains bad number of certificates")
+ }
+
+ if len(basicResp.TBSResponseData.Responses) != 1 {
+ return nil, ParseError("OCSP response contains bad number of responses")
+ }
+
+ ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ if ret.Certificate.PublicKeyAlgorithm != x509.RSA || !basicResp.SignatureAlgorithm.Algorithm.Equal(idSHA1WithRSA) {
+ return nil, x509.UnsupportedAlgorithmError{}
+ }
+
+ hashType := crypto.SHA1
+ h := hashType.New()
+
+ pub := ret.Certificate.PublicKey.(*rsa.PublicKey)
+ h.Write(basicResp.TBSResponseData.Raw)
+ digest := h.Sum(nil)
+ signature := basicResp.Signature.RightAlign()
+
+ if rsa.VerifyPKCS1v15(pub, hashType, digest, signature) != nil {
+ return nil, ParseError("bad OCSP signature")
+ }
+
+ r := basicResp.TBSResponseData.Responses[0]
+
+ ret.SerialNumber = r.CertID.SerialNumber.Bytes
+
+ switch {
+ case bool(r.Good):
+ ret.Status = Good
+ case bool(r.Unknown):
+ ret.Status = Unknown
+ default:
+ ret.Status = Revoked
+ ret.RevokedAt = r.Revoked.RevocationTime
+ ret.RevocationReason = r.Revoked.Reason
+ }
+
+ ret.ProducedAt = basicResp.TBSResponseData.ProducedAt
+ ret.ThisUpdate = r.ThisUpdate
+ ret.NextUpdate = r.NextUpdate
+
+ return ret, nil
+}
diff --git a/ocsp/ocsp_test.go b/ocsp/ocsp_test.go
new file mode 100644
index 0000000..f0e9f94
--- /dev/null
+++ b/ocsp/ocsp_test.go
@@ -0,0 +1,107 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ocsp
+
+import (
+ "bytes"
+ "encoding/hex"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestOCSPDecode(t *testing.T) {
+ responseBytes, _ := hex.DecodeString(ocspResponseHex)
+ resp, err := ParseResponse(responseBytes)
+ if err != nil {
+ t.Error(err)
+ }
+
+ expected := Response{
+ Status: 0,
+ SerialNumber: []byte{0x1, 0xd0, 0xfa},
+ RevocationReason: 0,
+ ThisUpdate: time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC),
+ NextUpdate: time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC),
+ }
+
+ if !reflect.DeepEqual(resp.ThisUpdate, expected.ThisUpdate) {
+ t.Errorf("resp.ThisUpdate: got %d, want %d", resp.ThisUpdate, expected.ThisUpdate)
+ }
+
+ if !reflect.DeepEqual(resp.NextUpdate, expected.NextUpdate) {
+ t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, expected.NextUpdate)
+ }
+
+ if resp.Status != expected.Status {
+ t.Errorf("resp.Status: got %d, want %d", resp.Status, expected.Status)
+ }
+
+ if !bytes.Equal(resp.SerialNumber, expected.SerialNumber) {
+ t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, expected.SerialNumber)
+ }
+
+ if resp.RevocationReason != expected.RevocationReason {
+ t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, expected.RevocationReason)
+ }
+}
+
+// This OCSP response was taken from Thawte's public OCSP responder.
+// To recreate:
+// $ openssl s_client -tls1 -showcerts -servername www.google.com -connect www.google.com:443
+// Copy and paste the first certificate into /tmp/cert.crt and the second into
+// /tmp/intermediate.crt
+// $ openssl ocsp -issuer /tmp/intermediate.crt -cert /tmp/cert.crt -url http://ocsp.thawte.com -resp_text -respout /tmp/ocsp.der
+// Then hex encode the result:
+// $ python -c 'print file("/tmp/ocsp.der", "r").read().encode("hex")'
+
+const ocspResponseHex = "308206bc0a0100a08206b5308206b106092b0601050507300101048206a23082069e3081" +
+ "c9a14e304c310b300906035504061302494c31163014060355040a130d5374617274436f" +
+ "6d204c74642e312530230603550403131c5374617274436f6d20436c6173732031204f43" +
+ "5350205369676e6572180f32303130303730373137333531375a30663064303c30090605" +
+ "2b0e03021a050004146568874f40750f016a3475625e1f5c93e5a26d580414eb4234d098" +
+ "b0ab9ff41b6b08f7cc642eef0e2c45020301d0fa8000180f323031303037303731353031" +
+ "30355aa011180f32303130303730373138333531375a300d06092a864886f70d01010505" +
+ "000382010100ab557ff070d1d7cebbb5f0ec91a15c3fed22eb2e1b8244f1b84545f013a4" +
+ "fb46214c5e3fbfbebb8a56acc2b9db19f68fd3c3201046b3824d5ba689f99864328710cb" +
+ "467195eb37d84f539e49f859316b32964dc3e47e36814ce94d6c56dd02733b1d0802f7ff" +
+ "4eebdbbd2927dcf580f16cbc290f91e81b53cb365e7223f1d6e20a88ea064104875e0145" +
+ "672b20fc14829d51ca122f5f5d77d3ad6c83889c55c7dc43680ba2fe3cef8b05dbcabdc0" +
+ "d3e09aaf9725597f8c858c2fa38c0d6aed2e6318194420dd1a1137445d13e1c97ab47896" +
+ "17a4e08925f46f867b72e3a4dc1f08cb870b2b0717f7207faa0ac512e628a029aba7457a" +
+ "e63dcf3281e2162d9349a08204ba308204b6308204b23082039aa003020102020101300d" +
+ "06092a864886f70d010105050030818c310b300906035504061302494c31163014060355" +
+ "040a130d5374617274436f6d204c74642e312b3029060355040b13225365637572652044" +
+ "69676974616c204365727469666963617465205369676e696e6731383036060355040313" +
+ "2f5374617274436f6d20436c6173732031205072696d61727920496e7465726d65646961" +
+ "746520536572766572204341301e170d3037313032353030323330365a170d3132313032" +
+ "333030323330365a304c310b300906035504061302494c31163014060355040a130d5374" +
+ "617274436f6d204c74642e312530230603550403131c5374617274436f6d20436c617373" +
+ "2031204f435350205369676e657230820122300d06092a864886f70d0101010500038201" +
+ "0f003082010a0282010100b9561b4c45318717178084e96e178df2255e18ed8d8ecc7c2b" +
+ "7b51a6c1c2e6bf0aa3603066f132fe10ae97b50e99fa24b83fc53dd2777496387d14e1c3" +
+ "a9b6a4933e2ac12413d085570a95b8147414a0bc007c7bcf222446ef7f1a156d7ea1c577" +
+ "fc5f0facdfd42eb0f5974990cb2f5cefebceef4d1bdc7ae5c1075c5a99a93171f2b0845b" +
+ "4ff0864e973fcfe32f9d7511ff87a3e943410c90a4493a306b6944359340a9ca96f02b66" +
+ "ce67f028df2980a6aaee8d5d5d452b8b0eb93f923cc1e23fcccbdbe7ffcb114d08fa7a6a" +
+ "3c404f825d1a0e715935cf623a8c7b59670014ed0622f6089a9447a7a19010f7fe58f841" +
+ "29a2765ea367824d1c3bb2fda308530203010001a382015c30820158300c0603551d1301" +
+ "01ff04023000300b0603551d0f0404030203a8301e0603551d250417301506082b060105" +
+ "0507030906092b0601050507300105301d0603551d0e0416041445e0a36695414c5dd449" +
+ "bc00e33cdcdbd2343e173081a80603551d230481a030819d8014eb4234d098b0ab9ff41b" +
+ "6b08f7cc642eef0e2c45a18181a47f307d310b300906035504061302494c311630140603" +
+ "55040a130d5374617274436f6d204c74642e312b3029060355040b132253656375726520" +
+ "4469676974616c204365727469666963617465205369676e696e67312930270603550403" +
+ "13205374617274436f6d2043657274696669636174696f6e20417574686f726974798201" +
+ "0a30230603551d12041c301a8618687474703a2f2f7777772e737461727473736c2e636f" +
+ "6d2f302c06096086480186f842010d041f161d5374617274436f6d205265766f63617469" +
+ "6f6e20417574686f72697479300d06092a864886f70d01010505000382010100182d2215" +
+ "8f0fc0291324fa8574c49bb8ff2835085adcbf7b7fc4191c397ab6951328253fffe1e5ec" +
+ "2a7da0d50fca1a404e6968481366939e666c0a6209073eca57973e2fefa9ed1718e8176f" +
+ "1d85527ff522c08db702e3b2b180f1cbff05d98128252cf0f450f7dd2772f4188047f19d" +
+ "c85317366f94bc52d60f453a550af58e308aaab00ced33040b62bf37f5b1ab2a4f7f0f80" +
+ "f763bf4d707bc8841d7ad9385ee2a4244469260b6f2bf085977af9074796048ecc2f9d48" +
+ "a1d24ce16e41a9941568fec5b42771e118f16c106a54ccc339a4b02166445a167902e75e" +
+ "6d8620b0825dcd18a069b90fd851d10fa8effd409deec02860d26d8d833f304b10669b42"
diff --git a/openpgp/armor/armor.go b/openpgp/armor/armor.go
new file mode 100644
index 0000000..8bfab05
--- /dev/null
+++ b/openpgp/armor/armor.go
@@ -0,0 +1,219 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
+// very similar to PEM except that it has an additional CRC checksum.
+package armor
+
+import (
+ "bufio"
+ "bytes"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "encoding/base64"
+ "io"
+)
+
+// A Block represents an OpenPGP armored structure.
+//
+// The encoded form is:
+// -----BEGIN Type-----
+// Headers
+//
+// base64-encoded Bytes
+// '=' base64 encoded checksum
+// -----END Type-----
+// where Headers is a possibly empty sequence of Key: Value lines.
+//
+// Since the armored data can be very large, this package presents a streaming
+// interface.
+type Block struct {
+ Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE").
+ Header map[string]string // Optional headers.
+ Body io.Reader // A Reader from which the contents can be read
+ lReader lineReader
+ oReader openpgpReader
+}
+
+var ArmorCorrupt error = errors.StructuralError("armor invalid")
+
+const crc24Init = 0xb704ce
+const crc24Poly = 0x1864cfb
+const crc24Mask = 0xffffff
+
+// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
+func crc24(crc uint32, d []byte) uint32 {
+ for _, b := range d {
+ crc ^= uint32(b) << 16
+ for i := 0; i < 8; i++ {
+ crc <<= 1
+ if crc&0x1000000 != 0 {
+ crc ^= crc24Poly
+ }
+ }
+ }
+ return crc
+}
+
+var armorStart = []byte("-----BEGIN ")
+var armorEnd = []byte("-----END ")
+var armorEndOfLine = []byte("-----")
+
+// lineReader wraps a line based reader. It watches for the end of an armor
+// block and records the expected CRC value.
+type lineReader struct {
+ in *bufio.Reader
+ buf []byte
+ eof bool
+ crc uint32
+}
+
+func (l *lineReader) Read(p []byte) (n int, err error) {
+ if l.eof {
+ return 0, io.EOF
+ }
+
+ if len(l.buf) > 0 {
+ n = copy(p, l.buf)
+ l.buf = l.buf[n:]
+ return
+ }
+
+ line, isPrefix, err := l.in.ReadLine()
+ if err != nil {
+ return
+ }
+ if isPrefix {
+ return 0, ArmorCorrupt
+ }
+
+ if len(line) == 5 && line[0] == '=' {
+ // This is the checksum line
+ var expectedBytes [3]byte
+ var m int
+ m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
+ if m != 3 || err != nil {
+ return
+ }
+ l.crc = uint32(expectedBytes[0])<<16 |
+ uint32(expectedBytes[1])<<8 |
+ uint32(expectedBytes[2])
+
+ line, _, err = l.in.ReadLine()
+ if err != nil && err != io.EOF {
+ return
+ }
+ if !bytes.HasPrefix(line, armorEnd) {
+ return 0, ArmorCorrupt
+ }
+
+ l.eof = true
+ return 0, io.EOF
+ }
+
+ if len(line) > 64 {
+ return 0, ArmorCorrupt
+ }
+
+ n = copy(p, line)
+ bytesToSave := len(line) - n
+ if bytesToSave > 0 {
+ if cap(l.buf) < bytesToSave {
+ l.buf = make([]byte, 0, bytesToSave)
+ }
+ l.buf = l.buf[0:bytesToSave]
+ copy(l.buf, line[n:])
+ }
+
+ return
+}
+
+// openpgpReader passes Read calls to the underlying base64 decoder, but keeps
+// a running CRC of the resulting data and checks the CRC against the value
+// found by the lineReader at EOF.
+type openpgpReader struct {
+ lReader *lineReader
+ b64Reader io.Reader
+ currentCRC uint32
+}
+
+func (r *openpgpReader) Read(p []byte) (n int, err error) {
+ n, err = r.b64Reader.Read(p)
+ r.currentCRC = crc24(r.currentCRC, p[:n])
+
+ if err == io.EOF {
+ if r.lReader.crc != uint32(r.currentCRC&crc24Mask) {
+ return 0, ArmorCorrupt
+ }
+ }
+
+ return
+}
+
+// Decode reads a PGP armored block from the given Reader. It will ignore
+// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
+// given Reader is not usable after calling this function: an arbitrary amount
+// of data may have been read past the end of the block.
+func Decode(in io.Reader) (p *Block, err error) {
+ r, _ := bufio.NewReaderSize(in, 100)
+ var line []byte
+ ignoreNext := false
+
+TryNextBlock:
+ p = nil
+
+ // Skip leading garbage
+ for {
+ ignoreThis := ignoreNext
+ line, ignoreNext, err = r.ReadLine()
+ if err != nil {
+ return
+ }
+ if ignoreNext || ignoreThis {
+ continue
+ }
+ line = bytes.TrimSpace(line)
+ if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
+ break
+ }
+ }
+
+ p = new(Block)
+ p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
+ p.Header = make(map[string]string)
+ nextIsContinuation := false
+ var lastKey string
+
+ // Read headers
+ for {
+ isContinuation := nextIsContinuation
+ line, nextIsContinuation, err = r.ReadLine()
+ if err != nil {
+ p = nil
+ return
+ }
+ if isContinuation {
+ p.Header[lastKey] += string(line)
+ continue
+ }
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 {
+ break
+ }
+
+ i := bytes.Index(line, []byte(": "))
+ if i == -1 {
+ goto TryNextBlock
+ }
+ lastKey = string(line[:i])
+ p.Header[lastKey] = string(line[i+2:])
+ }
+
+ p.lReader.in = r
+ p.oReader.currentCRC = crc24Init
+ p.oReader.lReader = &p.lReader
+ p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
+ p.Body = &p.oReader
+
+ return
+}
diff --git a/openpgp/armor/armor_test.go b/openpgp/armor/armor_test.go
new file mode 100644
index 0000000..9334e94
--- /dev/null
+++ b/openpgp/armor/armor_test.go
@@ -0,0 +1,95 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package armor
+
+import (
+ "bytes"
+ "hash/adler32"
+ "io/ioutil"
+ "testing"
+)
+
+func TestDecodeEncode(t *testing.T) {
+ buf := bytes.NewBuffer([]byte(armorExample1))
+ result, err := Decode(buf)
+ if err != nil {
+ t.Error(err)
+ }
+ expectedType := "PGP SIGNATURE"
+ if result.Type != expectedType {
+ t.Errorf("result.Type: got:%s want:%s", result.Type, expectedType)
+ }
+ if len(result.Header) != 1 {
+ t.Errorf("len(result.Header): got:%d want:1", len(result.Header))
+ }
+ v, ok := result.Header["Version"]
+ if !ok || v != "GnuPG v1.4.10 (GNU/Linux)" {
+ t.Errorf("result.Header: got:%#v", result.Header)
+ }
+
+ contents, err := ioutil.ReadAll(result.Body)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if adler32.Checksum(contents) != 0x27b144be {
+ t.Errorf("contents: got: %x", contents)
+ }
+
+ buf = bytes.NewBuffer(nil)
+ w, err := Encode(buf, result.Type, result.Header)
+ if err != nil {
+ t.Error(err)
+ }
+ _, err = w.Write(contents)
+ if err != nil {
+ t.Error(err)
+ }
+ w.Close()
+
+ if !bytes.Equal(buf.Bytes(), []byte(armorExample1)) {
+ t.Errorf("got: %s\nwant: %s", string(buf.Bytes()), armorExample1)
+ }
+}
+
+func TestLongHeader(t *testing.T) {
+ buf := bytes.NewBuffer([]byte(armorLongLine))
+ result, err := Decode(buf)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ value, ok := result.Header["Version"]
+ if !ok {
+ t.Errorf("missing Version header")
+ }
+ if value != longValueExpected {
+ t.Errorf("got: %s want: %s", value, longValueExpected)
+ }
+}
+
+const armorExample1 = `-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iJwEAAECAAYFAk1Fv/0ACgkQo01+GMIMMbsYTwQAiAw+QAaNfY6WBdplZ/uMAccm
+4g+81QPmTSGHnetSb6WBiY13kVzK4HQiZH8JSkmmroMLuGeJwsRTEL4wbjRyUKEt
+p1xwUZDECs234F1xiG5enc5SGlRtP7foLBz9lOsjx+LEcA4sTl5/2eZR9zyFZqWW
+TxRjs+fJCIFuo71xb1g=
+=/teI
+-----END PGP SIGNATURE-----`
+
+const armorLongLine = `-----BEGIN PGP SIGNATURE-----
+Version: 0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz
+
+iQEcBAABAgAGBQJMtFESAAoJEKsQXJGvOPsVj40H/1WW6jaMXv4BW+1ueDSMDwM8
+kx1fLOXbVM5/Kn5LStZNt1jWWnpxdz7eq3uiqeCQjmqUoRde3YbB2EMnnwRbAhpp
+cacnAvy9ZQ78OTxUdNW1mhX5bS6q1MTEJnl+DcyigD70HG/yNNQD7sOPMdYQw0TA
+byQBwmLwmTsuZsrYqB68QyLHI+DUugn+kX6Hd2WDB62DKa2suoIUIHQQCd/ofwB3
+WfCYInXQKKOSxu2YOg2Eb4kLNhSMc1i9uKUWAH+sdgJh7NBgdoE4MaNtBFkHXRvv
+okWuf3+xA9ksp1npSY/mDvgHijmjvtpRDe6iUeqfCn8N9u9CBg8geANgaG8+QA4=
+=wfQG
+-----END PGP SIGNATURE-----`
+
+const longValueExpected = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz"
diff --git a/openpgp/armor/encode.go b/openpgp/armor/encode.go
new file mode 100644
index 0000000..6f07582
--- /dev/null
+++ b/openpgp/armor/encode.go
@@ -0,0 +1,160 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package armor
+
+import (
+ "encoding/base64"
+ "io"
+)
+
+var armorHeaderSep = []byte(": ")
+var blockEnd = []byte("\n=")
+var newline = []byte("\n")
+var armorEndOfLineOut = []byte("-----\n")
+
+// writeSlices writes its arguments to the given Writer.
+func writeSlices(out io.Writer, slices ...[]byte) (err error) {
+ for _, s := range slices {
+ _, err = out.Write(s)
+ if err != nil {
+ return err
+ }
+ }
+ return
+}
+
+// lineBreaker breaks data across several lines, all of the same byte length
+// (except possibly the last). Lines are broken with a single '\n'.
+type lineBreaker struct {
+ lineLength int
+ line []byte
+ used int
+ out io.Writer
+ haveWritten bool
+}
+
+func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
+ return &lineBreaker{
+ lineLength: lineLength,
+ line: make([]byte, lineLength),
+ used: 0,
+ out: out,
+ }
+}
+
+func (l *lineBreaker) Write(b []byte) (n int, err error) {
+ n = len(b)
+
+ if n == 0 {
+ return
+ }
+
+ if l.used == 0 && l.haveWritten {
+ _, err = l.out.Write([]byte{'\n'})
+ if err != nil {
+ return
+ }
+ }
+
+ if l.used+len(b) < l.lineLength {
+ l.used += copy(l.line[l.used:], b)
+ return
+ }
+
+ l.haveWritten = true
+ _, err = l.out.Write(l.line[0:l.used])
+ if err != nil {
+ return
+ }
+ excess := l.lineLength - l.used
+ l.used = 0
+
+ _, err = l.out.Write(b[0:excess])
+ if err != nil {
+ return
+ }
+
+ _, err = l.Write(b[excess:])
+ return
+}
+
+func (l *lineBreaker) Close() (err error) {
+ if l.used > 0 {
+ _, err = l.out.Write(l.line[0:l.used])
+ if err != nil {
+ return
+ }
+ }
+
+ return
+}
+
+// encoding keeps track of a running CRC24 over the data which has been written
+// to it and outputs a OpenPGP checksum when closed, followed by an armor
+// trailer.
+//
+// It's built into a stack of io.Writers:
+// encoding -> base64 encoder -> lineBreaker -> out
+type encoding struct {
+ out io.Writer
+ breaker *lineBreaker
+ b64 io.WriteCloser
+ crc uint32
+ blockType []byte
+}
+
+func (e *encoding) Write(data []byte) (n int, err error) {
+ e.crc = crc24(e.crc, data)
+ return e.b64.Write(data)
+}
+
+func (e *encoding) Close() (err error) {
+ err = e.b64.Close()
+ if err != nil {
+ return
+ }
+ e.breaker.Close()
+
+ var checksumBytes [3]byte
+ checksumBytes[0] = byte(e.crc >> 16)
+ checksumBytes[1] = byte(e.crc >> 8)
+ checksumBytes[2] = byte(e.crc)
+
+ var b64ChecksumBytes [4]byte
+ base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
+
+ return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
+}
+
+// Encode returns a WriteCloser which will encode the data written to it in
+// OpenPGP armor.
+func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
+ bType := []byte(blockType)
+ err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
+ if err != nil {
+ return
+ }
+
+ for k, v := range headers {
+ err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
+ if err != nil {
+ return
+ }
+ }
+
+ _, err = out.Write(newline)
+ if err != nil {
+ return
+ }
+
+ e := &encoding{
+ out: out,
+ breaker: newLineBreaker(out, 64),
+ crc: crc24Init,
+ blockType: bType,
+ }
+ e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
+ return e, nil
+}
diff --git a/openpgp/canonical_text.go b/openpgp/canonical_text.go
new file mode 100644
index 0000000..e601e38
--- /dev/null
+++ b/openpgp/canonical_text.go
@@ -0,0 +1,59 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import "hash"
+
+// NewCanonicalTextHash reformats text written to it into the canonical
+// form and then applies the hash h. See RFC 4880, section 5.2.1.
+func NewCanonicalTextHash(h hash.Hash) hash.Hash {
+ return &canonicalTextHash{h, 0}
+}
+
+type canonicalTextHash struct {
+ h hash.Hash
+ s int
+}
+
+var newline = []byte{'\r', '\n'}
+
+func (cth *canonicalTextHash) Write(buf []byte) (int, error) {
+ start := 0
+
+ for i, c := range buf {
+ switch cth.s {
+ case 0:
+ if c == '\r' {
+ cth.s = 1
+ } else if c == '\n' {
+ cth.h.Write(buf[start:i])
+ cth.h.Write(newline)
+ start = i + 1
+ }
+ case 1:
+ cth.s = 0
+ }
+ }
+
+ cth.h.Write(buf[start:])
+ return len(buf), nil
+}
+
+func (cth *canonicalTextHash) Sum(in []byte) []byte {
+ return cth.h.Sum(in)
+}
+
+func (cth *canonicalTextHash) Reset() {
+ cth.h.Reset()
+ cth.s = 0
+}
+
+func (cth *canonicalTextHash) Size() int {
+ return cth.h.Size()
+}
+
+func (cth *canonicalTextHash) BlockSize() int {
+ return cth.h.BlockSize()
+}
diff --git a/openpgp/canonical_text_test.go b/openpgp/canonical_text_test.go
new file mode 100644
index 0000000..8f3ba2a
--- /dev/null
+++ b/openpgp/canonical_text_test.go
@@ -0,0 +1,52 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+ "bytes"
+ "testing"
+)
+
+type recordingHash struct {
+ buf *bytes.Buffer
+}
+
+func (r recordingHash) Write(b []byte) (n int, err error) {
+ return r.buf.Write(b)
+}
+
+func (r recordingHash) Sum(in []byte) []byte {
+ return append(in, r.buf.Bytes()...)
+}
+
+func (r recordingHash) Reset() {
+ panic("shouldn't be called")
+}
+
+func (r recordingHash) Size() int {
+ panic("shouldn't be called")
+}
+
+func (r recordingHash) BlockSize() int {
+ panic("shouldn't be called")
+}
+
+func testCanonicalText(t *testing.T, input, expected string) {
+ r := recordingHash{bytes.NewBuffer(nil)}
+ c := NewCanonicalTextHash(r)
+ c.Write([]byte(input))
+ result := c.Sum(nil)
+ if expected != string(result) {
+ t.Errorf("input: %x got: %x want: %x", input, result, expected)
+ }
+}
+
+func TestCanonicalText(t *testing.T) {
+ testCanonicalText(t, "foo\n", "foo\r\n")
+ testCanonicalText(t, "foo", "foo")
+ testCanonicalText(t, "foo\r\n", "foo\r\n")
+ testCanonicalText(t, "foo\r\nbar", "foo\r\nbar")
+ testCanonicalText(t, "foo\r\nbar\n\n", "foo\r\nbar\r\n\r\n")
+}
diff --git a/openpgp/elgamal/elgamal.go b/openpgp/elgamal/elgamal.go
new file mode 100644
index 0000000..a553bde
--- /dev/null
+++ b/openpgp/elgamal/elgamal.go
@@ -0,0 +1,122 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package elgamal implements ElGamal encryption, suitable for OpenPGP,
+// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on
+// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31,
+// n. 4, 1985, pp. 469-472.
+//
+// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it
+// unsuitable for other protocols. RSA should be used in preference in any
+// case.
+package elgamal
+
+import (
+ "crypto/rand"
+ "crypto/subtle"
+ "errors"
+ "io"
+ "math/big"
+)
+
+// PublicKey represents an ElGamal public key.
+type PublicKey struct {
+ G, P, Y *big.Int
+}
+
+// PrivateKey represents an ElGamal private key.
+type PrivateKey struct {
+ PublicKey
+ X *big.Int
+}
+
+// Encrypt encrypts the given message to the given public key. The result is a
+// pair of integers. Errors can result from reading random, or because msg is
+// too large to be encrypted to the public key.
+func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) {
+ pLen := (pub.P.BitLen() + 7) / 8
+ if len(msg) > pLen-11 {
+ err = errors.New("elgamal: message too long")
+ return
+ }
+
+ // EM = 0x02 || PS || 0x00 || M
+ em := make([]byte, pLen-1)
+ em[0] = 2
+ ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):]
+ err = nonZeroRandomBytes(ps, random)
+ if err != nil {
+ return
+ }
+ em[len(em)-len(msg)-1] = 0
+ copy(mm, msg)
+
+ m := new(big.Int).SetBytes(em)
+
+ k, err := rand.Int(random, pub.P)
+ if err != nil {
+ return
+ }
+
+ c1 = new(big.Int).Exp(pub.G, k, pub.P)
+ s := new(big.Int).Exp(pub.Y, k, pub.P)
+ c2 = s.Mul(s, m)
+ c2.Mod(c2, pub.P)
+
+ return
+}
+
+// Decrypt takes two integers, resulting from an ElGamal encryption, and
+// returns the plaintext of the message. An error can result only if the
+// ciphertext is invalid. Users should keep in mind that this is a padding
+// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can
+// be used to break the cryptosystem. See ``Chosen Ciphertext Attacks
+// Against Protocols Based on the RSA Encryption Standard PKCS #1'', Daniel
+// Bleichenbacher, Advances in Cryptology (Crypto '98),
+func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
+ s := new(big.Int).Exp(c1, priv.X, priv.P)
+ s.ModInverse(s, priv.P)
+ s.Mul(s, c2)
+ s.Mod(s, priv.P)
+ em := s.Bytes()
+
+ firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2)
+
+ // The remainder of the plaintext must be a string of non-zero random
+ // octets, followed by a 0, followed by the message.
+ // lookingForIndex: 1 iff we are still looking for the zero.
+ // index: the offset of the first zero byte.
+ var lookingForIndex, index int
+ lookingForIndex = 1
+
+ for i := 1; i < len(em); i++ {
+ equals0 := subtle.ConstantTimeByteEq(em[i], 0)
+ index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index)
+ lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex)
+ }
+
+ if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 {
+ return nil, errors.New("elgamal: decryption error")
+ }
+ return em[index+1:], nil
+}
+
+// nonZeroRandomBytes fills the given slice with non-zero random octets.
+func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) {
+ _, err = io.ReadFull(rand, s)
+ if err != nil {
+ return
+ }
+
+ for i := 0; i < len(s); i++ {
+ for s[i] == 0 {
+ _, err = io.ReadFull(rand, s[i:i+1])
+ if err != nil {
+ return
+ }
+ }
+ }
+
+ return
+}
diff --git a/openpgp/elgamal/elgamal_test.go b/openpgp/elgamal/elgamal_test.go
new file mode 100644
index 0000000..c4f99f5
--- /dev/null
+++ b/openpgp/elgamal/elgamal_test.go
@@ -0,0 +1,49 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package elgamal
+
+import (
+ "bytes"
+ "crypto/rand"
+ "math/big"
+ "testing"
+)
+
+// This is the 1024-bit MODP group from RFC 5114, section 2.1:
+const primeHex = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371"
+
+const generatorHex = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CFF14266D31266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28AD662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5"
+
+func fromHex(hex string) *big.Int {
+ n, ok := new(big.Int).SetString(hex, 16)
+ if !ok {
+ panic("failed to parse hex number")
+ }
+ return n
+}
+
+func TestEncryptDecrypt(t *testing.T) {
+ priv := &PrivateKey{
+ PublicKey: PublicKey{
+ G: fromHex(generatorHex),
+ P: fromHex(primeHex),
+ },
+ X: fromHex("42"),
+ }
+ priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P)
+
+ message := []byte("hello world")
+ c1, c2, err := Encrypt(rand.Reader, &priv.PublicKey, message)
+ if err != nil {
+ t.Errorf("error encrypting: %s", err)
+ }
+ message2, err := Decrypt(priv, c1, c2)
+ if err != nil {
+ t.Errorf("error decrypting: %s", err)
+ }
+ if !bytes.Equal(message2, message) {
+ t.Errorf("decryption failed, got: %x, want: %x", message2, message)
+ }
+}
diff --git a/openpgp/errors/errors.go b/openpgp/errors/errors.go
new file mode 100644
index 0000000..b9016bd
--- /dev/null
+++ b/openpgp/errors/errors.go
@@ -0,0 +1,64 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package errors contains common error types for the OpenPGP packages.
+package errors
+
+import (
+ "strconv"
+)
+
+// A StructuralError is returned when OpenPGP data is found to be syntactically
+// invalid.
+type StructuralError string
+
+func (s StructuralError) Error() string {
+ return "OpenPGP data invalid: " + string(s)
+}
+
+// UnsupportedError indicates that, although the OpenPGP data is valid, it
+// makes use of currently unimplemented features.
+type UnsupportedError string
+
+func (s UnsupportedError) Error() string {
+ return "OpenPGP feature unsupported: " + string(s)
+}
+
+// InvalidArgumentError indicates that the caller is in error and passed an
+// incorrect value.
+type InvalidArgumentError string
+
+func (i InvalidArgumentError) Error() string {
+ return "OpenPGP argument invalid: " + string(i)
+}
+
+// SignatureError indicates that a syntactically valid signature failed to
+// validate.
+type SignatureError string
+
+func (b SignatureError) Error() string {
+ return "OpenPGP signature invalid: " + string(b)
+}
+
+type keyIncorrectError int
+
+func (ki keyIncorrectError) Error() string {
+ return "the given key was incorrect"
+}
+
+var ErrKeyIncorrect error = keyIncorrectError(0)
+
+type unknownIssuerError int
+
+func (unknownIssuerError) Error() string {
+ return "signature make by unknown entity"
+}
+
+var ErrUnknownIssuer error = unknownIssuerError(0)
+
+type UnknownPacketTypeError uint8
+
+func (upte UnknownPacketTypeError) Error() string {
+ return "unknown OpenPGP packet type: " + strconv.Itoa(int(upte))
+}
diff --git a/openpgp/keys.go b/openpgp/keys.go
new file mode 100644
index 0000000..5ae8fbd
--- /dev/null
+++ b/openpgp/keys.go
@@ -0,0 +1,546 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+ "code.google.com/p/go.crypto/openpgp/armor"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "code.google.com/p/go.crypto/openpgp/packet"
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "io"
+ "time"
+)
+
+// PublicKeyType is the armor type for a PGP public key.
+var PublicKeyType = "PGP PUBLIC KEY BLOCK"
+
+// PrivateKeyType is the armor type for a PGP private key.
+var PrivateKeyType = "PGP PRIVATE KEY BLOCK"
+
+// An Entity represents the components of an OpenPGP key: a primary public key
+// (which must be a signing key), one or more identities claimed by that key,
+// and zero or more subkeys, which may be encryption keys.
+type Entity struct {
+ PrimaryKey *packet.PublicKey
+ PrivateKey *packet.PrivateKey
+ Identities map[string]*Identity // indexed by Identity.Name
+ Subkeys []Subkey
+}
+
+// An Identity represents an identity claimed by an Entity and zero or more
+// assertions by other entities about that claim.
+type Identity struct {
+ Name string // by convention, has the form "Full Name (comment) <email@example.com>"
+ UserId *packet.UserId
+ SelfSignature *packet.Signature
+ Signatures []*packet.Signature
+}
+
+// A Subkey is an additional public key in an Entity. Subkeys can be used for
+// encryption.
+type Subkey struct {
+ PublicKey *packet.PublicKey
+ PrivateKey *packet.PrivateKey
+ Sig *packet.Signature
+}
+
+// A Key identifies a specific public key in an Entity. This is either the
+// Entity's primary key or a subkey.
+type Key struct {
+ Entity *Entity
+ PublicKey *packet.PublicKey
+ PrivateKey *packet.PrivateKey
+ SelfSignature *packet.Signature
+}
+
+// A KeyRing provides access to public and private keys.
+type KeyRing interface {
+ // KeysById returns the set of keys that have the given key id.
+ KeysById(id uint64) []Key
+ // DecryptionKeys returns all private keys that are valid for
+ // decryption.
+ DecryptionKeys() []Key
+}
+
+// primaryIdentity returns the Identity marked as primary or the first identity
+// if none are so marked.
+func (e *Entity) primaryIdentity() *Identity {
+ var firstIdentity *Identity
+ for _, ident := range e.Identities {
+ if firstIdentity == nil {
+ firstIdentity = ident
+ }
+ if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
+ return ident
+ }
+ }
+ return firstIdentity
+}
+
+// encryptionKey returns the best candidate Key for encrypting a message to the
+// given Entity.
+func (e *Entity) encryptionKey() Key {
+ candidateSubkey := -1
+
+ for i, subkey := range e.Subkeys {
+ if subkey.Sig.FlagsValid && subkey.Sig.FlagEncryptCommunications && subkey.PublicKey.PubKeyAlgo.CanEncrypt() {
+ candidateSubkey = i
+ break
+ }
+ }
+
+ i := e.primaryIdentity()
+
+ if e.PrimaryKey.PubKeyAlgo.CanEncrypt() {
+ // If we don't have any candidate subkeys for encryption and
+ // the primary key doesn't have any usage metadata then we
+ // assume that the primary key is ok. Or, if the primary key is
+ // marked as ok to encrypt to, then we can obviously use it.
+ if candidateSubkey == -1 && !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications && i.SelfSignature.FlagsValid {
+ return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}
+ }
+ }
+
+ if candidateSubkey != -1 {
+ subkey := e.Subkeys[candidateSubkey]
+ return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}
+ }
+
+ // This Entity appears to be signing only.
+ return Key{}
+}
+
+// signingKey return the best candidate Key for signing a message with this
+// Entity.
+func (e *Entity) signingKey() Key {
+ candidateSubkey := -1
+
+ for i, subkey := range e.Subkeys {
+ if subkey.Sig.FlagsValid && subkey.Sig.FlagSign && subkey.PublicKey.PubKeyAlgo.CanSign() {
+ candidateSubkey = i
+ break
+ }
+ }
+
+ i := e.primaryIdentity()
+
+ // If we have no candidate subkey then we assume that it's ok to sign
+ // with the primary key.
+ if candidateSubkey == -1 || i.SelfSignature.FlagsValid && i.SelfSignature.FlagSign {
+ return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}
+ }
+
+ subkey := e.Subkeys[candidateSubkey]
+ return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}
+}
+
+// An EntityList contains one or more Entities.
+type EntityList []*Entity
+
+// KeysById returns the set of keys that have the given key id.
+func (el EntityList) KeysById(id uint64) (keys []Key) {
+ for _, e := range el {
+ if e.PrimaryKey.KeyId == id {
+ var selfSig *packet.Signature
+ for _, ident := range e.Identities {
+ if selfSig == nil {
+ selfSig = ident.SelfSignature
+ } else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
+ selfSig = ident.SelfSignature
+ break
+ }
+ }
+ keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig})
+ }
+
+ for _, subKey := range e.Subkeys {
+ if subKey.PublicKey.KeyId == id {
+ keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+ }
+ }
+ }
+ return
+}
+
+// DecryptionKeys returns all private keys that are valid for decryption.
+func (el EntityList) DecryptionKeys() (keys []Key) {
+ for _, e := range el {
+ for _, subKey := range e.Subkeys {
+ if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
+ keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+ }
+ }
+ }
+ return
+}
+
+// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file.
+func ReadArmoredKeyRing(r io.Reader) (EntityList, error) {
+ block, err := armor.Decode(r)
+ if err == io.EOF {
+ return nil, errors.InvalidArgumentError("no armored data found")
+ }
+ if err != nil {
+ return nil, err
+ }
+ if block.Type != PublicKeyType && block.Type != PrivateKeyType {
+ return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type)
+ }
+
+ return ReadKeyRing(block.Body)
+}
+
+// ReadKeyRing reads one or more public/private keys. Unsupported keys are
+// ignored as long as at least a single valid key is found.
+func ReadKeyRing(r io.Reader) (el EntityList, err error) {
+ packets := packet.NewReader(r)
+ var lastUnsupportedError error
+
+ for {
+ var e *Entity
+ e, err = readEntity(packets)
+ if err != nil {
+ if _, ok := err.(errors.UnsupportedError); ok {
+ lastUnsupportedError = err
+ err = readToNextPublicKey(packets)
+ }
+ if err == io.EOF {
+ err = nil
+ break
+ }
+ if err != nil {
+ el = nil
+ break
+ }
+ } else {
+ el = append(el, e)
+ }
+ }
+
+ if len(el) == 0 && err == nil {
+ err = lastUnsupportedError
+ }
+ return
+}
+
+// readToNextPublicKey reads packets until the start of the entity and leaves
+// the first packet of the new entity in the Reader.
+func readToNextPublicKey(packets *packet.Reader) (err error) {
+ var p packet.Packet
+ for {
+ p, err = packets.Next()
+ if err == io.EOF {
+ return
+ } else if err != nil {
+ if _, ok := err.(errors.UnsupportedError); ok {
+ err = nil
+ continue
+ }
+ return
+ }
+
+ if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey {
+ packets.Unread(p)
+ return
+ }
+ }
+
+ panic("unreachable")
+}
+
+// readEntity reads an entity (public key, identities, subkeys etc) from the
+// given Reader.
+func readEntity(packets *packet.Reader) (*Entity, error) {
+ e := new(Entity)
+ e.Identities = make(map[string]*Identity)
+
+ p, err := packets.Next()
+ if err != nil {
+ return nil, err
+ }
+
+ var ok bool
+ if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
+ if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
+ packets.Unread(p)
+ return nil, errors.StructuralError("first packet was not a public/private key")
+ } else {
+ e.PrimaryKey = &e.PrivateKey.PublicKey
+ }
+ }
+
+ if !e.PrimaryKey.PubKeyAlgo.CanSign() {
+ return nil, errors.StructuralError("primary key cannot be used for signatures")
+ }
+
+ var current *Identity
+EachPacket:
+ for {
+ p, err := packets.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return nil, err
+ }
+
+ switch pkt := p.(type) {
+ case *packet.UserId:
+ current = new(Identity)
+ current.Name = pkt.Id
+ current.UserId = pkt
+ e.Identities[pkt.Id] = current
+
+ for {
+ p, err = packets.Next()
+ if err == io.EOF {
+ return nil, io.ErrUnexpectedEOF
+ } else if err != nil {
+ return nil, err
+ }
+
+ sig, ok := p.(*packet.Signature)
+ if !ok {
+ return nil, errors.StructuralError("user ID packet not followed by self-signature")
+ }
+
+ if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId {
+ if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, sig); err != nil {
+ return nil, errors.StructuralError("user ID self-signature invalid: " + err.Error())
+ }
+ current.SelfSignature = sig
+ break
+ }
+ current.Signatures = append(current.Signatures, sig)
+ }
+ case *packet.Signature:
+ if current == nil {
+ return nil, errors.StructuralError("signature packet found before user id packet")
+ }
+ current.Signatures = append(current.Signatures, pkt)
+ case *packet.PrivateKey:
+ if pkt.IsSubkey == false {
+ packets.Unread(p)
+ break EachPacket
+ }
+ err = addSubkey(e, packets, &pkt.PublicKey, pkt)
+ if err != nil {
+ return nil, err
+ }
+ case *packet.PublicKey:
+ if pkt.IsSubkey == false {
+ packets.Unread(p)
+ break EachPacket
+ }
+ err = addSubkey(e, packets, pkt, nil)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ // we ignore unknown packets
+ }
+ }
+
+ if len(e.Identities) == 0 {
+ return nil, errors.StructuralError("entity without any identities")
+ }
+
+ return e, nil
+}
+
+func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error {
+ var subKey Subkey
+ subKey.PublicKey = pub
+ subKey.PrivateKey = priv
+ p, err := packets.Next()
+ if err == io.EOF {
+ return io.ErrUnexpectedEOF
+ }
+ if err != nil {
+ return errors.StructuralError("subkey signature invalid: " + err.Error())
+ }
+ var ok bool
+ subKey.Sig, ok = p.(*packet.Signature)
+ if !ok {
+ return errors.StructuralError("subkey packet not followed by signature")
+ }
+ if subKey.Sig.SigType != packet.SigTypeSubkeyBinding {
+ return errors.StructuralError("subkey signature with wrong type")
+ }
+ err = e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, subKey.Sig)
+ if err != nil {
+ return errors.StructuralError("subkey signature invalid: " + err.Error())
+ }
+ e.Subkeys = append(e.Subkeys, subKey)
+ return nil
+}
+
+const defaultRSAKeyBits = 2048
+
+// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
+// single identity composed of the given full name, comment and email, any of
+// which may be empty but must not contain any of "()<>\x00".
+func NewEntity(rand io.Reader, currentTime time.Time, name, comment, email string) (*Entity, error) {
+ uid := packet.NewUserId(name, comment, email)
+ if uid == nil {
+ return nil, errors.InvalidArgumentError("user id field contained invalid characters")
+ }
+ signingPriv, err := rsa.GenerateKey(rand, defaultRSAKeyBits)
+ if err != nil {
+ return nil, err
+ }
+ encryptingPriv, err := rsa.GenerateKey(rand, defaultRSAKeyBits)
+ if err != nil {
+ return nil, err
+ }
+
+ e := &Entity{
+ PrimaryKey: packet.NewRSAPublicKey(currentTime, &signingPriv.PublicKey),
+ PrivateKey: packet.NewRSAPrivateKey(currentTime, signingPriv),
+ Identities: make(map[string]*Identity),
+ }
+ isPrimaryId := true
+ e.Identities[uid.Id] = &Identity{
+ Name: uid.Name,
+ UserId: uid,
+ SelfSignature: &packet.Signature{
+ CreationTime: currentTime,
+ SigType: packet.SigTypePositiveCert,
+ PubKeyAlgo: packet.PubKeyAlgoRSA,
+ Hash: crypto.SHA256,
+ IsPrimaryId: &isPrimaryId,
+ FlagsValid: true,
+ FlagSign: true,
+ FlagCertify: true,
+ IssuerKeyId: &e.PrimaryKey.KeyId,
+ },
+ }
+
+ e.Subkeys = make([]Subkey, 1)
+ e.Subkeys[0] = Subkey{
+ PublicKey: packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey),
+ PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv),
+ Sig: &packet.Signature{
+ CreationTime: currentTime,
+ SigType: packet.SigTypeSubkeyBinding,
+ PubKeyAlgo: packet.PubKeyAlgoRSA,
+ Hash: crypto.SHA256,
+ FlagsValid: true,
+ FlagEncryptStorage: true,
+ FlagEncryptCommunications: true,
+ IssuerKeyId: &e.PrimaryKey.KeyId,
+ },
+ }
+ e.Subkeys[0].PublicKey.IsSubkey = true
+ e.Subkeys[0].PrivateKey.IsSubkey = true
+
+ return e, nil
+}
+
+// SerializePrivate serializes an Entity, including private key material, to
+// the given Writer. For now, it must only be used on an Entity returned from
+// NewEntity.
+func (e *Entity) SerializePrivate(w io.Writer) (err error) {
+ err = e.PrivateKey.Serialize(w)
+ if err != nil {
+ return
+ }
+ for _, ident := range e.Identities {
+ err = ident.UserId.Serialize(w)
+ if err != nil {
+ return
+ }
+ err = ident.SelfSignature.SignUserId(rand.Reader, ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
+ if err != nil {
+ return
+ }
+ err = ident.SelfSignature.Serialize(w)
+ if err != nil {
+ return
+ }
+ }
+ for _, subkey := range e.Subkeys {
+ err = subkey.PrivateKey.Serialize(w)
+ if err != nil {
+ return
+ }
+ err = subkey.Sig.SignKey(rand.Reader, subkey.PublicKey, e.PrivateKey)
+ if err != nil {
+ return
+ }
+ err = subkey.Sig.Serialize(w)
+ if err != nil {
+ return
+ }
+ }
+ return nil
+}
+
+// Serialize writes the public part of the given Entity to w. (No private
+// key material will be output).
+func (e *Entity) Serialize(w io.Writer) error {
+ err := e.PrimaryKey.Serialize(w)
+ if err != nil {
+ return err
+ }
+ for _, ident := range e.Identities {
+ err = ident.UserId.Serialize(w)
+ if err != nil {
+ return err
+ }
+ err = ident.SelfSignature.Serialize(w)
+ if err != nil {
+ return err
+ }
+ for _, sig := range ident.Signatures {
+ err = sig.Serialize(w)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ for _, subkey := range e.Subkeys {
+ err = subkey.PublicKey.Serialize(w)
+ if err != nil {
+ return err
+ }
+ err = subkey.Sig.Serialize(w)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SignIdentity adds a signature to e, from signer, attesting that identity is
+// associated with e. The provided identity must already be an element of
+// e.Identities and the private key of signer must have been decrypted if
+// necessary.
+func (e *Entity) SignIdentity(identity string, signer *Entity) error {
+ if signer.PrivateKey == nil {
+ return errors.InvalidArgumentError("signing Entity must have a private key")
+ }
+ if signer.PrivateKey.Encrypted {
+ return errors.InvalidArgumentError("signing Entity's private key must be decrypted")
+ }
+ ident, ok := e.Identities[identity]
+ if !ok {
+ return errors.InvalidArgumentError("given identity string not found in Entity")
+ }
+
+ sig := &packet.Signature{
+ SigType: packet.SigTypeGenericCert,
+ PubKeyAlgo: signer.PrivateKey.PubKeyAlgo,
+ Hash: crypto.SHA256,
+ CreationTime: time.Now(),
+ IssuerKeyId: &signer.PrivateKey.KeyId,
+ }
+ if err := sig.SignKey(rand.Reader, e.PrimaryKey, signer.PrivateKey); err != nil {
+ return err
+ }
+ ident.Signatures = append(ident.Signatures, sig)
+ return nil
+}
diff --git a/openpgp/packet/compressed.go b/openpgp/packet/compressed.go
new file mode 100644
index 0000000..816229e
--- /dev/null
+++ b/openpgp/packet/compressed.go
@@ -0,0 +1,38 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "compress/flate"
+ "compress/zlib"
+ "io"
+ "strconv"
+)
+
+// Compressed represents a compressed OpenPGP packet. The decompressed contents
+// will contain more OpenPGP packets. See RFC 4880, section 5.6.
+type Compressed struct {
+ Body io.Reader
+}
+
+func (c *Compressed) parse(r io.Reader) error {
+ var buf [1]byte
+ _, err := readFull(r, buf[:])
+ if err != nil {
+ return err
+ }
+
+ switch buf[0] {
+ case 1:
+ c.Body = flate.NewReader(r)
+ case 2:
+ c.Body, err = zlib.NewReader(r)
+ default:
+ err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
+ }
+
+ return err
+}
diff --git a/openpgp/packet/compressed_test.go b/openpgp/packet/compressed_test.go
new file mode 100644
index 0000000..cb2d70b
--- /dev/null
+++ b/openpgp/packet/compressed_test.go
@@ -0,0 +1,41 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "encoding/hex"
+ "io"
+ "io/ioutil"
+ "testing"
+)
+
+func TestCompressed(t *testing.T) {
+ packet, err := Read(readerFromHex(compressedHex))
+ if err != nil {
+ t.Errorf("failed to read Compressed: %s", err)
+ return
+ }
+
+ c, ok := packet.(*Compressed)
+ if !ok {
+ t.Error("didn't find Compressed packet")
+ return
+ }
+
+ contents, err := ioutil.ReadAll(c.Body)
+ if err != nil && err != io.EOF {
+ t.Error(err)
+ return
+ }
+
+ expected, _ := hex.DecodeString(compressedExpectedHex)
+ if !bytes.Equal(expected, contents) {
+ t.Errorf("got:%x want:%x", contents, expected)
+ }
+}
+
+const compressedHex = "a3013b2d90c4e02b72e25f727e5e496a5e49b11e1700"
+const compressedExpectedHex = "cb1062004d14c8fe636f6e74656e74732e0a"
diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go
new file mode 100644
index 0000000..b4a4eff
--- /dev/null
+++ b/openpgp/packet/encrypted_key.go
@@ -0,0 +1,167 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "code.google.com/p/go.crypto/openpgp/elgamal"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "crypto/rand"
+ "crypto/rsa"
+ "encoding/binary"
+ "io"
+ "math/big"
+ "strconv"
+)
+
+const encryptedKeyVersion = 3
+
+// EncryptedKey represents a public-key encrypted session key. See RFC 4880,
+// section 5.1.
+type EncryptedKey struct {
+ KeyId uint64
+ Algo PublicKeyAlgorithm
+ CipherFunc CipherFunction // only valid after a successful Decrypt
+ Key []byte // only valid after a successful Decrypt
+
+ encryptedMPI1, encryptedMPI2 []byte
+}
+
+func (e *EncryptedKey) parse(r io.Reader) (err error) {
+ var buf [10]byte
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ if buf[0] != encryptedKeyVersion {
+ return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
+ }
+ e.KeyId = binary.BigEndian.Uint64(buf[1:9])
+ e.Algo = PublicKeyAlgorithm(buf[9])
+ switch e.Algo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ e.encryptedMPI1, _, err = readMPI(r)
+ case PubKeyAlgoElGamal:
+ e.encryptedMPI1, _, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ e.encryptedMPI2, _, err = readMPI(r)
+ }
+ _, err = consumeAll(r)
+ return
+}
+
+func checksumKeyMaterial(key []byte) uint16 {
+ var checksum uint16
+ for _, v := range key {
+ checksum += uint16(v)
+ }
+ return checksum
+}
+
+// Decrypt decrypts an encrypted session key with the given private key. The
+// private key must have been decrypted first.
+func (e *EncryptedKey) Decrypt(priv *PrivateKey) error {
+ var err error
+ var b []byte
+
+ // TODO(agl): use session key decryption routines here to avoid
+ // padding oracle attacks.
+ switch priv.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ b, err = rsa.DecryptPKCS1v15(rand.Reader, priv.PrivateKey.(*rsa.PrivateKey), e.encryptedMPI1)
+ case PubKeyAlgoElGamal:
+ c1 := new(big.Int).SetBytes(e.encryptedMPI1)
+ c2 := new(big.Int).SetBytes(e.encryptedMPI2)
+ b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2)
+ default:
+ err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
+ }
+
+ if err != nil {
+ return err
+ }
+
+ e.CipherFunc = CipherFunction(b[0])
+ e.Key = b[1 : len(b)-2]
+ expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
+ checksum := checksumKeyMaterial(e.Key)
+ if checksum != expectedChecksum {
+ return errors.StructuralError("EncryptedKey checksum incorrect")
+ }
+
+ return nil
+}
+
+// SerializeEncryptedKey serializes an encrypted key packet to w that contains
+// key, encrypted to pub.
+func SerializeEncryptedKey(w io.Writer, rand io.Reader, pub *PublicKey, cipherFunc CipherFunction, key []byte) error {
+ var buf [10]byte
+ buf[0] = encryptedKeyVersion
+ binary.BigEndian.PutUint64(buf[1:9], pub.KeyId)
+ buf[9] = byte(pub.PubKeyAlgo)
+
+ keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */ )
+ keyBlock[0] = byte(cipherFunc)
+ copy(keyBlock[1:], key)
+ checksum := checksumKeyMaterial(key)
+ keyBlock[1+len(key)] = byte(checksum >> 8)
+ keyBlock[1+len(key)+1] = byte(checksum)
+
+ switch pub.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ return serializeEncryptedKeyRSA(w, rand, buf, pub.PublicKey.(*rsa.PublicKey), keyBlock)
+ case PubKeyAlgoElGamal:
+ return serializeEncryptedKeyElGamal(w, rand, buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock)
+ case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly:
+ return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
+ }
+
+ return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
+}
+
+func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error {
+ cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)
+ if err != nil {
+ return errors.InvalidArgumentError("RSA encryption failed: " + err.Error())
+ }
+
+ packetLen := 10 /* header length */ + 2 /* mpi size */ + len(cipherText)
+
+ err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(header[:])
+ if err != nil {
+ return err
+ }
+ return writeMPI(w, 8*uint16(len(cipherText)), cipherText)
+}
+
+func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error {
+ c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock)
+ if err != nil {
+ return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
+ }
+
+ packetLen := 10 /* header length */
+ packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8
+ packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8
+
+ err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(header[:])
+ if err != nil {
+ return err
+ }
+ err = writeBig(w, c1)
+ if err != nil {
+ return err
+ }
+ return writeBig(w, c2)
+}
diff --git a/openpgp/packet/encrypted_key_test.go b/openpgp/packet/encrypted_key_test.go
new file mode 100644
index 0000000..2b8eff7
--- /dev/null
+++ b/openpgp/packet/encrypted_key_test.go
@@ -0,0 +1,126 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/rsa"
+ "fmt"
+ "math/big"
+ "testing"
+)
+
+func bigFromBase10(s string) *big.Int {
+ b, ok := new(big.Int).SetString(s, 10)
+ if !ok {
+ panic("bigFromBase10 failed")
+ }
+ return b
+}
+
+var encryptedKeyPub = rsa.PublicKey{
+ E: 65537,
+ N: bigFromBase10("115804063926007623305902631768113868327816898845124614648849934718568541074358183759250136204762053879858102352159854352727097033322663029387610959884180306668628526686121021235757016368038585212410610742029286439607686208110250133174279811431933746643015923132833417396844716207301518956640020862630546868823"),
+}
+
+var encryptedKeyRSAPriv = &rsa.PrivateKey{
+ PublicKey: encryptedKeyPub,
+ D: bigFromBase10("32355588668219869544751561565313228297765464314098552250409557267371233892496951383426602439009993875125222579159850054973310859166139474359774543943714622292329487391199285040721944491839695981199720170366763547754915493640685849961780092241140181198779299712578774460837139360803883139311171713302987058393"),
+}
+
+var encryptedKeyPriv = &PrivateKey{
+ PublicKey: PublicKey{
+ PubKeyAlgo: PubKeyAlgoRSA,
+ },
+ PrivateKey: encryptedKeyRSAPriv,
+}
+
+func TestDecryptingEncryptedKey(t *testing.T) {
+ const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8"
+ const expectedKeyHex = "d930363f7e0308c333b9618617ea728963d8df993665ae7be1092d4926fd864b"
+
+ p, err := Read(readerFromHex(encryptedKeyHex))
+ if err != nil {
+ t.Errorf("error from Read: %s", err)
+ return
+ }
+ ek, ok := p.(*EncryptedKey)
+ if !ok {
+ t.Errorf("didn't parse an EncryptedKey, got %#v", p)
+ return
+ }
+
+ if ek.KeyId != 0x2a67d68660df41c7 || ek.Algo != PubKeyAlgoRSA {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ err = ek.Decrypt(encryptedKeyPriv)
+ if err != nil {
+ t.Errorf("error from Decrypt: %s", err)
+ return
+ }
+
+ if ek.CipherFunc != CipherAES256 {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ keyHex := fmt.Sprintf("%x", ek.Key)
+ if keyHex != expectedKeyHex {
+ t.Errorf("bad key, got %s want %x", keyHex, expectedKeyHex)
+ }
+}
+
+func TestEncryptingEncryptedKey(t *testing.T) {
+ key := []byte{1, 2, 3, 4}
+ const expectedKeyHex = "01020304"
+ const keyId = 42
+
+ pub := &PublicKey{
+ PublicKey: &encryptedKeyPub,
+ KeyId: keyId,
+ PubKeyAlgo: PubKeyAlgoRSAEncryptOnly,
+ }
+
+ buf := new(bytes.Buffer)
+ err := SerializeEncryptedKey(buf, rand.Reader, pub, CipherAES128, key)
+ if err != nil {
+ t.Errorf("error writing encrypted key packet: %s", err)
+ }
+
+ p, err := Read(buf)
+ if err != nil {
+ t.Errorf("error from Read: %s", err)
+ return
+ }
+ ek, ok := p.(*EncryptedKey)
+ if !ok {
+ t.Errorf("didn't parse an EncryptedKey, got %#v", p)
+ return
+ }
+
+ if ek.KeyId != keyId || ek.Algo != PubKeyAlgoRSAEncryptOnly {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ err = ek.Decrypt(encryptedKeyPriv)
+ if err != nil {
+ t.Errorf("error from Decrypt: %s", err)
+ return
+ }
+
+ if ek.CipherFunc != CipherAES128 {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ keyHex := fmt.Sprintf("%x", ek.Key)
+ if keyHex != expectedKeyHex {
+ t.Errorf("bad key, got %s want %x", keyHex, expectedKeyHex)
+ }
+}
diff --git a/openpgp/packet/literal.go b/openpgp/packet/literal.go
new file mode 100644
index 0000000..1a9ec6e
--- /dev/null
+++ b/openpgp/packet/literal.go
@@ -0,0 +1,89 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "encoding/binary"
+ "io"
+)
+
+// LiteralData represents an encrypted file. See RFC 4880, section 5.9.
+type LiteralData struct {
+ IsBinary bool
+ FileName string
+ Time uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined.
+ Body io.Reader
+}
+
+// ForEyesOnly returns whether the contents of the LiteralData have been marked
+// as especially sensitive.
+func (l *LiteralData) ForEyesOnly() bool {
+ return l.FileName == "_CONSOLE"
+}
+
+func (l *LiteralData) parse(r io.Reader) (err error) {
+ var buf [256]byte
+
+ _, err = readFull(r, buf[:2])
+ if err != nil {
+ return
+ }
+
+ l.IsBinary = buf[0] == 'b'
+ fileNameLen := int(buf[1])
+
+ _, err = readFull(r, buf[:fileNameLen])
+ if err != nil {
+ return
+ }
+
+ l.FileName = string(buf[:fileNameLen])
+
+ _, err = readFull(r, buf[:4])
+ if err != nil {
+ return
+ }
+
+ l.Time = binary.BigEndian.Uint32(buf[:4])
+ l.Body = r
+ return
+}
+
+// SerializeLiteral serializes a literal data packet to w and returns a
+// WriteCloser to which the data itself can be written and which MUST be closed
+// on completion. The fileName is truncated to 255 bytes.
+func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) {
+ var buf [4]byte
+ buf[0] = 't'
+ if isBinary {
+ buf[0] = 'b'
+ }
+ if len(fileName) > 255 {
+ fileName = fileName[:255]
+ }
+ buf[1] = byte(len(fileName))
+
+ inner, err := serializeStreamHeader(w, packetTypeLiteralData)
+ if err != nil {
+ return
+ }
+
+ _, err = inner.Write(buf[:2])
+ if err != nil {
+ return
+ }
+ _, err = inner.Write([]byte(fileName))
+ if err != nil {
+ return
+ }
+ binary.BigEndian.PutUint32(buf[:], time)
+ _, err = inner.Write(buf[:])
+ if err != nil {
+ return
+ }
+
+ plaintext = inner
+ return
+}
diff --git a/openpgp/packet/one_pass_signature.go b/openpgp/packet/one_pass_signature.go
new file mode 100644
index 0000000..8b5c547
--- /dev/null
+++ b/openpgp/packet/one_pass_signature.go
@@ -0,0 +1,73 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "code.google.com/p/go.crypto/openpgp/s2k"
+ "crypto"
+ "encoding/binary"
+ "io"
+ "strconv"
+)
+
+// OnePassSignature represents a one-pass signature packet. See RFC 4880,
+// section 5.4.
+type OnePassSignature struct {
+ SigType SignatureType
+ Hash crypto.Hash
+ PubKeyAlgo PublicKeyAlgorithm
+ KeyId uint64
+ IsLast bool
+}
+
+const onePassSignatureVersion = 3
+
+func (ops *OnePassSignature) parse(r io.Reader) (err error) {
+ var buf [13]byte
+
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ if buf[0] != onePassSignatureVersion {
+ err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
+ }
+
+ var ok bool
+ ops.Hash, ok = s2k.HashIdToHash(buf[2])
+ if !ok {
+ return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
+ }
+
+ ops.SigType = SignatureType(buf[1])
+ ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3])
+ ops.KeyId = binary.BigEndian.Uint64(buf[4:12])
+ ops.IsLast = buf[12] != 0
+ return
+}
+
+// Serialize marshals the given OnePassSignature to w.
+func (ops *OnePassSignature) Serialize(w io.Writer) error {
+ var buf [13]byte
+ buf[0] = onePassSignatureVersion
+ buf[1] = uint8(ops.SigType)
+ var ok bool
+ buf[2], ok = s2k.HashToHashId(ops.Hash)
+ if !ok {
+ return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
+ }
+ buf[3] = uint8(ops.PubKeyAlgo)
+ binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
+ if ops.IsLast {
+ buf[12] = 1
+ }
+
+ if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil {
+ return err
+ }
+ _, err := w.Write(buf[:])
+ return err
+}
diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go
new file mode 100644
index 0000000..3f858f6
--- /dev/null
+++ b/openpgp/packet/packet.go
@@ -0,0 +1,482 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package packet implements parsing and serialization of OpenPGP packets, as
+// specified in RFC 4880.
+package packet
+
+import (
+ "code.google.com/p/go.crypto/cast5"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "crypto/aes"
+ "crypto/cipher"
+ "io"
+ "math/big"
+)
+
+// readFull is the same as io.ReadFull except that reading zero bytes returns
+// ErrUnexpectedEOF rather than EOF.
+func readFull(r io.Reader, buf []byte) (n int, err error) {
+ n, err = io.ReadFull(r, buf)
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2.
+func readLength(r io.Reader) (length int64, isPartial bool, err error) {
+ var buf [4]byte
+ _, err = readFull(r, buf[:1])
+ if err != nil {
+ return
+ }
+ switch {
+ case buf[0] < 192:
+ length = int64(buf[0])
+ case buf[0] < 224:
+ length = int64(buf[0]-192) << 8
+ _, err = readFull(r, buf[0:1])
+ if err != nil {
+ return
+ }
+ length += int64(buf[0]) + 192
+ case buf[0] < 255:
+ length = int64(1) << (buf[0] & 0x1f)
+ isPartial = true
+ default:
+ _, err = readFull(r, buf[0:4])
+ if err != nil {
+ return
+ }
+ length = int64(buf[0])<<24 |
+ int64(buf[1])<<16 |
+ int64(buf[2])<<8 |
+ int64(buf[3])
+ }
+ return
+}
+
+// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths.
+// The continuation lengths are parsed and removed from the stream and EOF is
+// returned at the end of the packet. See RFC 4880, section 4.2.2.4.
+type partialLengthReader struct {
+ r io.Reader
+ remaining int64
+ isPartial bool
+}
+
+func (r *partialLengthReader) Read(p []byte) (n int, err error) {
+ for r.remaining == 0 {
+ if !r.isPartial {
+ return 0, io.EOF
+ }
+ r.remaining, r.isPartial, err = readLength(r.r)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ toRead := int64(len(p))
+ if toRead > r.remaining {
+ toRead = r.remaining
+ }
+
+ n, err = r.r.Read(p[:int(toRead)])
+ r.remaining -= int64(n)
+ if n < int(toRead) && err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// partialLengthWriter writes a stream of data using OpenPGP partial lengths.
+// See RFC 4880, section 4.2.2.4.
+type partialLengthWriter struct {
+ w io.WriteCloser
+ lengthByte [1]byte
+}
+
+func (w *partialLengthWriter) Write(p []byte) (n int, err error) {
+ for len(p) > 0 {
+ for power := uint(14); power < 32; power-- {
+ l := 1 << power
+ if len(p) >= l {
+ w.lengthByte[0] = 224 + uint8(power)
+ _, err = w.w.Write(w.lengthByte[:])
+ if err != nil {
+ return
+ }
+ var m int
+ m, err = w.w.Write(p[:l])
+ n += m
+ if err != nil {
+ return
+ }
+ p = p[l:]
+ break
+ }
+ }
+ }
+ return
+}
+
+func (w *partialLengthWriter) Close() error {
+ w.lengthByte[0] = 0
+ _, err := w.w.Write(w.lengthByte[:])
+ if err != nil {
+ return err
+ }
+ return w.w.Close()
+}
+
+// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the
+// underlying Reader returns EOF before the limit has been reached.
+type spanReader struct {
+ r io.Reader
+ n int64
+}
+
+func (l *spanReader) Read(p []byte) (n int, err error) {
+ if l.n <= 0 {
+ return 0, io.EOF
+ }
+ if int64(len(p)) > l.n {
+ p = p[0:l.n]
+ }
+ n, err = l.r.Read(p)
+ l.n -= int64(n)
+ if l.n > 0 && err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// readHeader parses a packet header and returns an io.Reader which will return
+// the contents of the packet. See RFC 4880, section 4.2.
+func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) {
+ var buf [4]byte
+ _, err = io.ReadFull(r, buf[:1])
+ if err != nil {
+ return
+ }
+ if buf[0]&0x80 == 0 {
+ err = errors.StructuralError("tag byte does not have MSB set")
+ return
+ }
+ if buf[0]&0x40 == 0 {
+ // Old format packet
+ tag = packetType((buf[0] & 0x3f) >> 2)
+ lengthType := buf[0] & 3
+ if lengthType == 3 {
+ length = -1
+ contents = r
+ return
+ }
+ lengthBytes := 1 << lengthType
+ _, err = readFull(r, buf[0:lengthBytes])
+ if err != nil {
+ return
+ }
+ for i := 0; i < lengthBytes; i++ {
+ length <<= 8
+ length |= int64(buf[i])
+ }
+ contents = &spanReader{r, length}
+ return
+ }
+
+ // New format packet
+ tag = packetType(buf[0] & 0x3f)
+ length, isPartial, err := readLength(r)
+ if err != nil {
+ return
+ }
+ if isPartial {
+ contents = &partialLengthReader{
+ remaining: length,
+ isPartial: true,
+ r: r,
+ }
+ length = -1
+ } else {
+ contents = &spanReader{r, length}
+ }
+ return
+}
+
+// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section
+// 4.2.
+func serializeHeader(w io.Writer, ptype packetType, length int) (err error) {
+ var buf [6]byte
+ var n int
+
+ buf[0] = 0x80 | 0x40 | byte(ptype)
+ if length < 192 {
+ buf[1] = byte(length)
+ n = 2
+ } else if length < 8384 {
+ length -= 192
+ buf[1] = 192 + byte(length>>8)
+ buf[2] = byte(length)
+ n = 3
+ } else {
+ buf[1] = 255
+ buf[2] = byte(length >> 24)
+ buf[3] = byte(length >> 16)
+ buf[4] = byte(length >> 8)
+ buf[5] = byte(length)
+ n = 6
+ }
+
+ _, err = w.Write(buf[:n])
+ return
+}
+
+// serializeStreamHeader writes an OpenPGP packet header to w where the
+// length of the packet is unknown. It returns a io.WriteCloser which can be
+// used to write the contents of the packet. See RFC 4880, section 4.2.
+func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) {
+ var buf [1]byte
+ buf[0] = 0x80 | 0x40 | byte(ptype)
+ _, err = w.Write(buf[:])
+ if err != nil {
+ return
+ }
+ out = &partialLengthWriter{w: w}
+ return
+}
+
+// Packet represents an OpenPGP packet. Users are expected to try casting
+// instances of this interface to specific packet types.
+type Packet interface {
+ parse(io.Reader) error
+}
+
+// consumeAll reads from the given Reader until error, returning the number of
+// bytes read.
+func consumeAll(r io.Reader) (n int64, err error) {
+ var m int
+ var buf [1024]byte
+
+ for {
+ m, err = r.Read(buf[:])
+ n += int64(m)
+ if err == io.EOF {
+ err = nil
+ return
+ }
+ if err != nil {
+ return
+ }
+ }
+
+ panic("unreachable")
+}
+
+// packetType represents the numeric ids of the different OpenPGP packet types. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2
+type packetType uint8
+
+const (
+ packetTypeEncryptedKey packetType = 1
+ packetTypeSignature packetType = 2
+ packetTypeSymmetricKeyEncrypted packetType = 3
+ packetTypeOnePassSignature packetType = 4
+ packetTypePrivateKey packetType = 5
+ packetTypePublicKey packetType = 6
+ packetTypePrivateSubkey packetType = 7
+ packetTypeCompressed packetType = 8
+ packetTypeSymmetricallyEncrypted packetType = 9
+ packetTypeLiteralData packetType = 11
+ packetTypeUserId packetType = 13
+ packetTypePublicSubkey packetType = 14
+ packetTypeSymmetricallyEncryptedMDC packetType = 18
+)
+
+// Read reads a single OpenPGP packet from the given io.Reader. If there is an
+// error parsing a packet, the whole packet is consumed from the input.
+func Read(r io.Reader) (p Packet, err error) {
+ tag, _, contents, err := readHeader(r)
+ if err != nil {
+ return
+ }
+
+ switch tag {
+ case packetTypeEncryptedKey:
+ p = new(EncryptedKey)
+ case packetTypeSignature:
+ p = new(Signature)
+ case packetTypeSymmetricKeyEncrypted:
+ p = new(SymmetricKeyEncrypted)
+ case packetTypeOnePassSignature:
+ p = new(OnePassSignature)
+ case packetTypePrivateKey, packetTypePrivateSubkey:
+ pk := new(PrivateKey)
+ if tag == packetTypePrivateSubkey {
+ pk.IsSubkey = true
+ }
+ p = pk
+ case packetTypePublicKey, packetTypePublicSubkey:
+ pk := new(PublicKey)
+ if tag == packetTypePublicSubkey {
+ pk.IsSubkey = true
+ }
+ p = pk
+ case packetTypeCompressed:
+ p = new(Compressed)
+ case packetTypeSymmetricallyEncrypted:
+ p = new(SymmetricallyEncrypted)
+ case packetTypeLiteralData:
+ p = new(LiteralData)
+ case packetTypeUserId:
+ p = new(UserId)
+ case packetTypeSymmetricallyEncryptedMDC:
+ se := new(SymmetricallyEncrypted)
+ se.MDC = true
+ p = se
+ default:
+ err = errors.UnknownPacketTypeError(tag)
+ }
+ if p != nil {
+ err = p.parse(contents)
+ }
+ if err != nil {
+ consumeAll(contents)
+ }
+ return
+}
+
+// SignatureType represents the different semantic meanings of an OpenPGP
+// signature. See RFC 4880, section 5.2.1.
+type SignatureType uint8
+
+const (
+ SigTypeBinary SignatureType = 0
+ SigTypeText = 1
+ SigTypeGenericCert = 0x10
+ SigTypePersonaCert = 0x11
+ SigTypeCasualCert = 0x12
+ SigTypePositiveCert = 0x13
+ SigTypeSubkeyBinding = 0x18
+)
+
+// PublicKeyAlgorithm represents the different public key system specified for
+// OpenPGP. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12
+type PublicKeyAlgorithm uint8
+
+const (
+ PubKeyAlgoRSA PublicKeyAlgorithm = 1
+ PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2
+ PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3
+ PubKeyAlgoElGamal PublicKeyAlgorithm = 16
+ PubKeyAlgoDSA PublicKeyAlgorithm = 17
+)
+
+// CanEncrypt returns true if it's possible to encrypt a message to a public
+// key of the given type.
+func (pka PublicKeyAlgorithm) CanEncrypt() bool {
+ switch pka {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal:
+ return true
+ }
+ return false
+}
+
+// CanSign returns true if it's possible for a public key of the given type to
+// sign a message.
+func (pka PublicKeyAlgorithm) CanSign() bool {
+ switch pka {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
+ return true
+ }
+ return false
+}
+
+// CipherFunction represents the different block ciphers specified for OpenPGP. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13
+type CipherFunction uint8
+
+const (
+ CipherCAST5 CipherFunction = 3
+ CipherAES128 CipherFunction = 7
+ CipherAES192 CipherFunction = 8
+ CipherAES256 CipherFunction = 9
+)
+
+// KeySize returns the key size, in bytes, of cipher.
+func (cipher CipherFunction) KeySize() int {
+ switch cipher {
+ case CipherCAST5:
+ return cast5.KeySize
+ case CipherAES128:
+ return 16
+ case CipherAES192:
+ return 24
+ case CipherAES256:
+ return 32
+ }
+ return 0
+}
+
+// blockSize returns the block size, in bytes, of cipher.
+func (cipher CipherFunction) blockSize() int {
+ switch cipher {
+ case CipherCAST5:
+ return 8
+ case CipherAES128, CipherAES192, CipherAES256:
+ return 16
+ }
+ return 0
+}
+
+// new returns a fresh instance of the given cipher.
+func (cipher CipherFunction) new(key []byte) (block cipher.Block) {
+ switch cipher {
+ case CipherCAST5:
+ block, _ = cast5.NewCipher(key)
+ case CipherAES128, CipherAES192, CipherAES256:
+ block, _ = aes.NewCipher(key)
+ }
+ return
+}
+
+// readMPI reads a big integer from r. The bit length returned is the bit
+// length that was specified in r. This is preserved so that the integer can be
+// reserialized exactly.
+func readMPI(r io.Reader) (mpi []byte, bitLength uint16, err error) {
+ var buf [2]byte
+ _, err = readFull(r, buf[0:])
+ if err != nil {
+ return
+ }
+ bitLength = uint16(buf[0])<<8 | uint16(buf[1])
+ numBytes := (int(bitLength) + 7) / 8
+ mpi = make([]byte, numBytes)
+ _, err = readFull(r, mpi)
+ return
+}
+
+// mpiLength returns the length of the given *big.Int when serialized as an
+// MPI.
+func mpiLength(n *big.Int) (mpiLengthInBytes int) {
+ mpiLengthInBytes = 2 /* MPI length */
+ mpiLengthInBytes += (n.BitLen() + 7) / 8
+ return
+}
+
+// writeMPI serializes a big integer to w.
+func writeMPI(w io.Writer, bitLength uint16, mpiBytes []byte) (err error) {
+ _, err = w.Write([]byte{byte(bitLength >> 8), byte(bitLength)})
+ if err == nil {
+ _, err = w.Write(mpiBytes)
+ }
+ return
+}
+
+// writeBig serializes a *big.Int to w.
+func writeBig(w io.Writer, i *big.Int) error {
+ return writeMPI(w, uint16(i.BitLen()), i.Bytes())
+}
diff --git a/openpgp/packet/packet_test.go b/openpgp/packet/packet_test.go
new file mode 100644
index 0000000..c2fd72b
--- /dev/null
+++ b/openpgp/packet/packet_test.go
@@ -0,0 +1,255 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "testing"
+)
+
+func TestReadFull(t *testing.T) {
+ var out [4]byte
+
+ b := bytes.NewBufferString("foo")
+ n, err := readFull(b, out[:3])
+ if n != 3 || err != nil {
+ t.Errorf("full read failed n:%d err:%s", n, err)
+ }
+
+ b = bytes.NewBufferString("foo")
+ n, err = readFull(b, out[:4])
+ if n != 3 || err != io.ErrUnexpectedEOF {
+ t.Errorf("partial read failed n:%d err:%s", n, err)
+ }
+
+ b = bytes.NewBuffer(nil)
+ n, err = readFull(b, out[:3])
+ if n != 0 || err != io.ErrUnexpectedEOF {
+ t.Errorf("empty read failed n:%d err:%s", n, err)
+ }
+}
+
+func readerFromHex(s string) io.Reader {
+ data, err := hex.DecodeString(s)
+ if err != nil {
+ panic("readerFromHex: bad input")
+ }
+ return bytes.NewBuffer(data)
+}
+
+var readLengthTests = []struct {
+ hexInput string
+ length int64
+ isPartial bool
+ err error
+}{
+ {"", 0, false, io.ErrUnexpectedEOF},
+ {"1f", 31, false, nil},
+ {"c0", 0, false, io.ErrUnexpectedEOF},
+ {"c101", 256 + 1 + 192, false, nil},
+ {"e0", 1, true, nil},
+ {"e1", 2, true, nil},
+ {"e2", 4, true, nil},
+ {"ff", 0, false, io.ErrUnexpectedEOF},
+ {"ff00", 0, false, io.ErrUnexpectedEOF},
+ {"ff0000", 0, false, io.ErrUnexpectedEOF},
+ {"ff000000", 0, false, io.ErrUnexpectedEOF},
+ {"ff00000000", 0, false, nil},
+ {"ff01020304", 16909060, false, nil},
+}
+
+func TestReadLength(t *testing.T) {
+ for i, test := range readLengthTests {
+ length, isPartial, err := readLength(readerFromHex(test.hexInput))
+ if test.err != nil {
+ if err != test.err {
+ t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("%d: unexpected error: %s", i, err)
+ continue
+ }
+ if length != test.length || isPartial != test.isPartial {
+ t.Errorf("%d: bad result got:(%d,%t) want:(%d,%t)", i, length, isPartial, test.length, test.isPartial)
+ }
+ }
+}
+
+var partialLengthReaderTests = []struct {
+ hexInput string
+ err error
+ hexOutput string
+}{
+ {"e0", io.ErrUnexpectedEOF, ""},
+ {"e001", io.ErrUnexpectedEOF, ""},
+ {"e0010102", nil, "0102"},
+ {"ff00000000", nil, ""},
+ {"e10102e1030400", nil, "01020304"},
+ {"e101", io.ErrUnexpectedEOF, ""},
+}
+
+func TestPartialLengthReader(t *testing.T) {
+ for i, test := range partialLengthReaderTests {
+ r := &partialLengthReader{readerFromHex(test.hexInput), 0, true}
+ out, err := ioutil.ReadAll(r)
+ if test.err != nil {
+ if err != test.err {
+ t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("%d: unexpected error: %s", i, err)
+ continue
+ }
+
+ got := fmt.Sprintf("%x", out)
+ if got != test.hexOutput {
+ t.Errorf("%d: got:%s want:%s", i, test.hexOutput, got)
+ }
+ }
+}
+
+var readHeaderTests = []struct {
+ hexInput string
+ structuralError bool
+ unexpectedEOF bool
+ tag int
+ length int64
+ hexOutput string
+}{
+ {"", false, false, 0, 0, ""},
+ {"7f", true, false, 0, 0, ""},
+
+ // Old format headers
+ {"80", false, true, 0, 0, ""},
+ {"8001", false, true, 0, 1, ""},
+ {"800102", false, false, 0, 1, "02"},
+ {"81000102", false, false, 0, 1, "02"},
+ {"820000000102", false, false, 0, 1, "02"},
+ {"860000000102", false, false, 1, 1, "02"},
+ {"83010203", false, false, 0, -1, "010203"},
+
+ // New format headers
+ {"c0", false, true, 0, 0, ""},
+ {"c000", false, false, 0, 0, ""},
+ {"c00102", false, false, 0, 1, "02"},
+ {"c0020203", false, false, 0, 2, "0203"},
+ {"c00202", false, true, 0, 2, ""},
+ {"c3020203", false, false, 3, 2, "0203"},
+}
+
+func TestReadHeader(t *testing.T) {
+ for i, test := range readHeaderTests {
+ tag, length, contents, err := readHeader(readerFromHex(test.hexInput))
+ if test.structuralError {
+ if _, ok := err.(errors.StructuralError); ok {
+ continue
+ }
+ t.Errorf("%d: expected StructuralError, got:%s", i, err)
+ continue
+ }
+ if err != nil {
+ if len(test.hexInput) == 0 && err == io.EOF {
+ continue
+ }
+ if !test.unexpectedEOF || err != io.ErrUnexpectedEOF {
+ t.Errorf("%d: unexpected error from readHeader: %s", i, err)
+ }
+ continue
+ }
+ if int(tag) != test.tag || length != test.length {
+ t.Errorf("%d: got:(%d,%d) want:(%d,%d)", i, int(tag), length, test.tag, test.length)
+ continue
+ }
+
+ body, err := ioutil.ReadAll(contents)
+ if err != nil {
+ if !test.unexpectedEOF || err != io.ErrUnexpectedEOF {
+ t.Errorf("%d: unexpected error from contents: %s", i, err)
+ }
+ continue
+ }
+ if test.unexpectedEOF {
+ t.Errorf("%d: expected ErrUnexpectedEOF from contents but got no error", i)
+ continue
+ }
+ got := fmt.Sprintf("%x", body)
+ if got != test.hexOutput {
+ t.Errorf("%d: got:%s want:%s", i, got, test.hexOutput)
+ }
+ }
+}
+
+func TestSerializeHeader(t *testing.T) {
+ tag := packetTypePublicKey
+ lengths := []int{0, 1, 2, 64, 192, 193, 8000, 8384, 8385, 10000}
+
+ for _, length := range lengths {
+ buf := bytes.NewBuffer(nil)
+ serializeHeader(buf, tag, length)
+ tag2, length2, _, err := readHeader(buf)
+ if err != nil {
+ t.Errorf("length %d, err: %s", length, err)
+ }
+ if tag2 != tag {
+ t.Errorf("length %d, tag incorrect (got %d, want %d)", length, tag2, tag)
+ }
+ if int(length2) != length {
+ t.Errorf("length %d, length incorrect (got %d)", length, length2)
+ }
+ }
+}
+
+func TestPartialLengths(t *testing.T) {
+ buf := bytes.NewBuffer(nil)
+ w := new(partialLengthWriter)
+ w.w = noOpCloser{buf}
+
+ const maxChunkSize = 64
+
+ var b [maxChunkSize]byte
+ var n uint8
+ for l := 1; l <= maxChunkSize; l++ {
+ for i := 0; i < l; i++ {
+ b[i] = n
+ n++
+ }
+ m, err := w.Write(b[:l])
+ if m != l {
+ t.Errorf("short write got: %d want: %d", m, l)
+ }
+ if err != nil {
+ t.Errorf("error from write: %s", err)
+ }
+ }
+ w.Close()
+
+ want := (maxChunkSize * (maxChunkSize + 1)) / 2
+ copyBuf := bytes.NewBuffer(nil)
+ r := &partialLengthReader{buf, 0, true}
+ m, err := io.Copy(copyBuf, r)
+ if m != int64(want) {
+ t.Errorf("short copy got: %d want: %d", m, want)
+ }
+ if err != nil {
+ t.Errorf("error from copy: %s", err)
+ }
+
+ copyBytes := copyBuf.Bytes()
+ for i := 0; i < want; i++ {
+ if copyBytes[i] != uint8(i) {
+ t.Errorf("bad pattern in copy at %d", i)
+ break
+ }
+ }
+}
diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go
new file mode 100644
index 0000000..f76c9c5
--- /dev/null
+++ b/openpgp/packet/private_key.go
@@ -0,0 +1,310 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "code.google.com/p/go.crypto/openpgp/elgamal"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "code.google.com/p/go.crypto/openpgp/s2k"
+ "crypto/cipher"
+ "crypto/dsa"
+ "crypto/rsa"
+ "crypto/sha1"
+ "io"
+ "io/ioutil"
+ "math/big"
+ "strconv"
+ "time"
+)
+
+// PrivateKey represents a possibly encrypted private key. See RFC 4880,
+// section 5.5.3.
+type PrivateKey struct {
+ PublicKey
+ Encrypted bool // if true then the private key is unavailable until Decrypt has been called.
+ encryptedData []byte
+ cipher CipherFunction
+ s2k func(out, in []byte)
+ PrivateKey interface{} // An *rsa.PrivateKey or *dsa.PrivateKey.
+ sha1Checksum bool
+ iv []byte
+}
+
+func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
+ pk := new(PrivateKey)
+ pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey)
+ pk.PrivateKey = priv
+ return pk
+}
+
+func NewDSAPrivateKey(currentTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
+ pk := new(PrivateKey)
+ pk.PublicKey = *NewDSAPublicKey(currentTime, &priv.PublicKey)
+ pk.PrivateKey = priv
+ return pk
+}
+
+func (pk *PrivateKey) parse(r io.Reader) (err error) {
+ err = (&pk.PublicKey).parse(r)
+ if err != nil {
+ return
+ }
+ var buf [1]byte
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+
+ s2kType := buf[0]
+
+ switch s2kType {
+ case 0:
+ pk.s2k = nil
+ pk.Encrypted = false
+ case 254, 255:
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ pk.cipher = CipherFunction(buf[0])
+ pk.Encrypted = true
+ pk.s2k, err = s2k.Parse(r)
+ if err != nil {
+ return
+ }
+ if s2kType == 254 {
+ pk.sha1Checksum = true
+ }
+ default:
+ return errors.UnsupportedError("deprecated s2k function in private key")
+ }
+
+ if pk.Encrypted {
+ blockSize := pk.cipher.blockSize()
+ if blockSize == 0 {
+ return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
+ }
+ pk.iv = make([]byte, blockSize)
+ _, err = readFull(r, pk.iv)
+ if err != nil {
+ return
+ }
+ }
+
+ pk.encryptedData, err = ioutil.ReadAll(r)
+ if err != nil {
+ return
+ }
+
+ if !pk.Encrypted {
+ return pk.parsePrivateKey(pk.encryptedData)
+ }
+
+ return
+}
+
+func mod64kHash(d []byte) uint16 {
+ var h uint16
+ for _, b := range d {
+ h += uint16(b)
+ }
+ return h
+}
+
+func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
+ // TODO(agl): support encrypted private keys
+ buf := bytes.NewBuffer(nil)
+ err = pk.PublicKey.serializeWithoutHeaders(buf)
+ if err != nil {
+ return
+ }
+ buf.WriteByte(0 /* no encryption */ )
+
+ privateKeyBuf := bytes.NewBuffer(nil)
+
+ switch priv := pk.PrivateKey.(type) {
+ case *rsa.PrivateKey:
+ err = serializeRSAPrivateKey(privateKeyBuf, priv)
+ case *dsa.PrivateKey:
+ err = serializeDSAPrivateKey(privateKeyBuf, priv)
+ default:
+ err = errors.InvalidArgumentError("unknown private key type")
+ }
+ if err != nil {
+ return
+ }
+
+ ptype := packetTypePrivateKey
+ contents := buf.Bytes()
+ privateKeyBytes := privateKeyBuf.Bytes()
+ if pk.IsSubkey {
+ ptype = packetTypePrivateSubkey
+ }
+ err = serializeHeader(w, ptype, len(contents)+len(privateKeyBytes)+2)
+ if err != nil {
+ return
+ }
+ _, err = w.Write(contents)
+ if err != nil {
+ return
+ }
+ _, err = w.Write(privateKeyBytes)
+ if err != nil {
+ return
+ }
+
+ checksum := mod64kHash(privateKeyBytes)
+ var checksumBytes [2]byte
+ checksumBytes[0] = byte(checksum >> 8)
+ checksumBytes[1] = byte(checksum)
+ _, err = w.Write(checksumBytes[:])
+
+ return
+}
+
+func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error {
+ err := writeBig(w, priv.D)
+ if err != nil {
+ return err
+ }
+ err = writeBig(w, priv.Primes[1])
+ if err != nil {
+ return err
+ }
+ err = writeBig(w, priv.Primes[0])
+ if err != nil {
+ return err
+ }
+ return writeBig(w, priv.Precomputed.Qinv)
+}
+
+func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error {
+ return writeBig(w, priv.X)
+}
+
+// Decrypt decrypts an encrypted private key using a passphrase.
+func (pk *PrivateKey) Decrypt(passphrase []byte) error {
+ if !pk.Encrypted {
+ return nil
+ }
+
+ key := make([]byte, pk.cipher.KeySize())
+ pk.s2k(key, passphrase)
+ block := pk.cipher.new(key)
+ cfb := cipher.NewCFBDecrypter(block, pk.iv)
+
+ data := pk.encryptedData
+ cfb.XORKeyStream(data, data)
+
+ if pk.sha1Checksum {
+ if len(data) < sha1.Size {
+ return errors.StructuralError("truncated private key data")
+ }
+ h := sha1.New()
+ h.Write(data[:len(data)-sha1.Size])
+ sum := h.Sum(nil)
+ if !bytes.Equal(sum, data[len(data)-sha1.Size:]) {
+ return errors.StructuralError("private key checksum failure")
+ }
+ data = data[:len(data)-sha1.Size]
+ } else {
+ if len(data) < 2 {
+ return errors.StructuralError("truncated private key data")
+ }
+ var sum uint16
+ for i := 0; i < len(data)-2; i++ {
+ sum += uint16(data[i])
+ }
+ if data[len(data)-2] != uint8(sum>>8) ||
+ data[len(data)-1] != uint8(sum) {
+ return errors.StructuralError("private key checksum failure")
+ }
+ data = data[:len(data)-2]
+ }
+
+ return pk.parsePrivateKey(data)
+}
+
+func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
+ switch pk.PublicKey.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly:
+ return pk.parseRSAPrivateKey(data)
+ case PubKeyAlgoDSA:
+ return pk.parseDSAPrivateKey(data)
+ case PubKeyAlgoElGamal:
+ return pk.parseElGamalPrivateKey(data)
+ }
+ panic("impossible")
+}
+
+func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) {
+ rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey)
+ rsaPriv := new(rsa.PrivateKey)
+ rsaPriv.PublicKey = *rsaPub
+
+ buf := bytes.NewBuffer(data)
+ d, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+ p, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+ q, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+
+ rsaPriv.D = new(big.Int).SetBytes(d)
+ rsaPriv.Primes = make([]*big.Int, 2)
+ rsaPriv.Primes[0] = new(big.Int).SetBytes(p)
+ rsaPriv.Primes[1] = new(big.Int).SetBytes(q)
+ rsaPriv.Precompute()
+ pk.PrivateKey = rsaPriv
+ pk.Encrypted = false
+ pk.encryptedData = nil
+
+ return nil
+}
+
+func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) {
+ dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey)
+ dsaPriv := new(dsa.PrivateKey)
+ dsaPriv.PublicKey = *dsaPub
+
+ buf := bytes.NewBuffer(data)
+ x, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+
+ dsaPriv.X = new(big.Int).SetBytes(x)
+ pk.PrivateKey = dsaPriv
+ pk.Encrypted = false
+ pk.encryptedData = nil
+
+ return nil
+}
+
+func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
+ pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey)
+ priv := new(elgamal.PrivateKey)
+ priv.PublicKey = *pub
+
+ buf := bytes.NewBuffer(data)
+ x, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+
+ priv.X = new(big.Int).SetBytes(x)
+ pk.PrivateKey = priv
+ pk.Encrypted = false
+ pk.encryptedData = nil
+
+ return nil
+}
diff --git a/openpgp/packet/private_key_test.go b/openpgp/packet/private_key_test.go
new file mode 100644
index 0000000..35d8951
--- /dev/null
+++ b/openpgp/packet/private_key_test.go
@@ -0,0 +1,58 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "testing"
+ "time"
+)
+
+var privateKeyTests = []struct {
+ privateKeyHex string
+ creationTime time.Time
+}{
+ {
+ privKeyRSAHex,
+ time.Unix(0x4cc349a8, 0),
+ },
+ {
+ privKeyElGamalHex,
+ time.Unix(0x4df9ee1a, 0),
+ },
+}
+
+func TestPrivateKeyRead(t *testing.T) {
+ for i, test := range privateKeyTests {
+ packet, err := Read(readerFromHex(test.privateKeyHex))
+ if err != nil {
+ t.Errorf("#%d: failed to parse: %s", i, err)
+ continue
+ }
+
+ privKey := packet.(*PrivateKey)
+
+ if !privKey.Encrypted {
+ t.Errorf("#%d: private key isn't encrypted", i)
+ continue
+ }
+
+ err = privKey.Decrypt([]byte("testing"))
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt: %s", i, err)
+ continue
+ }
+
+ if !privKey.CreationTime.Equal(test.creationTime) || privKey.Encrypted {
+ t.Errorf("#%d: bad result, got: %#v", i, privKey)
+ }
+ }
+}
+
+// Generated with `gpg --export-secret-keys "Test Key 2"`
+const privKeyRSAHex = "9501fe044cc349a8010400b70ca0010e98c090008d45d1ee8f9113bd5861fd57b88bacb7c68658747663f1e1a3b5a98f32fda6472373c024b97359cd2efc88ff60f77751adfbf6af5e615e6a1408cfad8bf0cea30b0d5f53aa27ad59089ba9b15b7ebc2777a25d7b436144027e3bcd203909f147d0e332b240cf63d3395f5dfe0df0a6c04e8655af7eacdf0011010001fe0303024a252e7d475fd445607de39a265472aa74a9320ba2dac395faa687e9e0336aeb7e9a7397e511b5afd9dc84557c80ac0f3d4d7bfec5ae16f20d41c8c84a04552a33870b930420e230e179564f6d19bb153145e76c33ae993886c388832b0fa042ddda7f133924f3854481533e0ede31d51278c0519b29abc3bf53da673e13e3e1214b52413d179d7f66deee35cac8eacb060f78379d70ef4af8607e68131ff529439668fc39c9ce6dfef8a5ac234d234802cbfb749a26107db26406213ae5c06d4673253a3cbee1fcbae58d6ab77e38d6e2c0e7c6317c48e054edadb5a40d0d48acb44643d998139a8a66bb820be1f3f80185bc777d14b5954b60effe2448a036d565c6bc0b915fcea518acdd20ab07bc1529f561c58cd044f723109b93f6fd99f876ff891d64306b5d08f48bab59f38695e9109c4dec34013ba3153488ce070268381ba923ee1eb77125b36afcb4347ec3478c8f2735b06ef17351d872e577fa95d0c397c88c71b59629a36aec"
+
+// Generated by `gpg --export-secret-keys` followed by a manual extraction of
+// the ElGamal subkey from the packets.
+const privKeyElGamalHex = "9d0157044df9ee1a100400eb8e136a58ec39b582629cdadf830bc64e0a94ed8103ca8bb247b27b11b46d1d25297ef4bcc3071785ba0c0bedfe89eabc5287fcc0edf81ab5896c1c8e4b20d27d79813c7aede75320b33eaeeaa586edc00fd1036c10133e6ba0ff277245d0d59d04b2b3421b7244aca5f4a8d870c6f1c1fbff9e1c26699a860b9504f35ca1d700030503fd1ededd3b840795be6d9ccbe3c51ee42e2f39233c432b831ddd9c4e72b7025a819317e47bf94f9ee316d7273b05d5fcf2999c3a681f519b1234bbfa6d359b4752bd9c3f77d6b6456cde152464763414ca130f4e91d91041432f90620fec0e6d6b5116076c2985d5aeaae13be492b9b329efcaf7ee25120159a0a30cd976b42d7afe030302dae7eb80db744d4960c4df930d57e87fe81412eaace9f900e6c839817a614ddb75ba6603b9417c33ea7b6c93967dfa2bcff3fa3c74a5ce2c962db65b03aece14c96cbd0038fc"
diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go
new file mode 100644
index 0000000..a757090
--- /dev/null
+++ b/openpgp/packet/public_key.go
@@ -0,0 +1,414 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "code.google.com/p/go.crypto/openpgp/elgamal"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "crypto/dsa"
+ "crypto/rsa"
+ "crypto/sha1"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "io"
+ "math/big"
+ "strconv"
+ "time"
+)
+
+// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2.
+type PublicKey struct {
+ CreationTime time.Time
+ PubKeyAlgo PublicKeyAlgorithm
+ PublicKey interface{} // Either a *rsa.PublicKey or *dsa.PublicKey
+ Fingerprint [20]byte
+ KeyId uint64
+ IsSubkey bool
+
+ n, e, p, q, g, y parsedMPI
+}
+
+func fromBig(n *big.Int) parsedMPI {
+ return parsedMPI{
+ bytes: n.Bytes(),
+ bitLength: uint16(n.BitLen()),
+ }
+}
+
+// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
+func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey {
+ pk := &PublicKey{
+ CreationTime: creationTime,
+ PubKeyAlgo: PubKeyAlgoRSA,
+ PublicKey: pub,
+ n: fromBig(pub.N),
+ e: fromBig(big.NewInt(int64(pub.E))),
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return pk
+}
+
+// NewDSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
+func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey {
+ pk := &PublicKey{
+ CreationTime: creationTime,
+ PubKeyAlgo: PubKeyAlgoDSA,
+ PublicKey: pub,
+ p: fromBig(pub.P),
+ q: fromBig(pub.Q),
+ g: fromBig(pub.G),
+ y: fromBig(pub.Y),
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return pk
+}
+
+func (pk *PublicKey) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.5.2
+ var buf [6]byte
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ if buf[0] != 4 {
+ return errors.UnsupportedError("public key version")
+ }
+ pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
+ pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5])
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ err = pk.parseRSA(r)
+ case PubKeyAlgoDSA:
+ err = pk.parseDSA(r)
+ case PubKeyAlgoElGamal:
+ err = pk.parseElGamal(r)
+ default:
+ err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
+ }
+ if err != nil {
+ return
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return
+}
+
+func (pk *PublicKey) setFingerPrintAndKeyId() {
+ // RFC 4880, section 12.2
+ fingerPrint := sha1.New()
+ pk.SerializeSignaturePrefix(fingerPrint)
+ pk.serializeWithoutHeaders(fingerPrint)
+ copy(pk.Fingerprint[:], fingerPrint.Sum(nil))
+ pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20])
+}
+
+// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKey) parseRSA(r io.Reader) (err error) {
+ pk.n.bytes, pk.n.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.e.bytes, pk.e.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+
+ if len(pk.e.bytes) > 3 {
+ err = errors.UnsupportedError("large public exponent")
+ return
+ }
+ rsa := &rsa.PublicKey{
+ N: new(big.Int).SetBytes(pk.n.bytes),
+ E: 0,
+ }
+ for i := 0; i < len(pk.e.bytes); i++ {
+ rsa.E <<= 8
+ rsa.E |= int(pk.e.bytes[i])
+ }
+ pk.PublicKey = rsa
+ return
+}
+
+// parseDSA parses DSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKey) parseDSA(r io.Reader) (err error) {
+ pk.p.bytes, pk.p.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.q.bytes, pk.q.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.g.bytes, pk.g.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.y.bytes, pk.y.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+
+ dsa := new(dsa.PublicKey)
+ dsa.P = new(big.Int).SetBytes(pk.p.bytes)
+ dsa.Q = new(big.Int).SetBytes(pk.q.bytes)
+ dsa.G = new(big.Int).SetBytes(pk.g.bytes)
+ dsa.Y = new(big.Int).SetBytes(pk.y.bytes)
+ pk.PublicKey = dsa
+ return
+}
+
+// parseElGamal parses ElGamal public key material from the given Reader. See
+// RFC 4880, section 5.5.2.
+func (pk *PublicKey) parseElGamal(r io.Reader) (err error) {
+ pk.p.bytes, pk.p.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.g.bytes, pk.g.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.y.bytes, pk.y.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+
+ elgamal := new(elgamal.PublicKey)
+ elgamal.P = new(big.Int).SetBytes(pk.p.bytes)
+ elgamal.G = new(big.Int).SetBytes(pk.g.bytes)
+ elgamal.Y = new(big.Int).SetBytes(pk.y.bytes)
+ pk.PublicKey = elgamal
+ return
+}
+
+// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
+// The prefix is used when calculating a signature over this public key. See
+// RFC 4880, section 5.2.4.
+func (pk *PublicKey) SerializeSignaturePrefix(h hash.Hash) {
+ var pLength uint16
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ pLength += 2 + uint16(len(pk.n.bytes))
+ pLength += 2 + uint16(len(pk.e.bytes))
+ case PubKeyAlgoDSA:
+ pLength += 2 + uint16(len(pk.p.bytes))
+ pLength += 2 + uint16(len(pk.q.bytes))
+ pLength += 2 + uint16(len(pk.g.bytes))
+ pLength += 2 + uint16(len(pk.y.bytes))
+ case PubKeyAlgoElGamal:
+ pLength += 2 + uint16(len(pk.p.bytes))
+ pLength += 2 + uint16(len(pk.g.bytes))
+ pLength += 2 + uint16(len(pk.y.bytes))
+ default:
+ panic("unknown public key algorithm")
+ }
+ pLength += 6
+ h.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
+ return
+}
+
+func (pk *PublicKey) Serialize(w io.Writer) (err error) {
+ length := 6 // 6 byte header
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ length += 2 + len(pk.n.bytes)
+ length += 2 + len(pk.e.bytes)
+ case PubKeyAlgoDSA:
+ length += 2 + len(pk.p.bytes)
+ length += 2 + len(pk.q.bytes)
+ length += 2 + len(pk.g.bytes)
+ length += 2 + len(pk.y.bytes)
+ case PubKeyAlgoElGamal:
+ length += 2 + len(pk.p.bytes)
+ length += 2 + len(pk.g.bytes)
+ length += 2 + len(pk.y.bytes)
+ default:
+ panic("unknown public key algorithm")
+ }
+
+ packetType := packetTypePublicKey
+ if pk.IsSubkey {
+ packetType = packetTypePublicSubkey
+ }
+ err = serializeHeader(w, packetType, length)
+ if err != nil {
+ return
+ }
+ return pk.serializeWithoutHeaders(w)
+}
+
+// serializeWithoutHeaders marshals the PublicKey to w in the form of an
+// OpenPGP public key packet, not including the packet header.
+func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
+ var buf [6]byte
+ buf[0] = 4
+ t := uint32(pk.CreationTime.Unix())
+ buf[1] = byte(t >> 24)
+ buf[2] = byte(t >> 16)
+ buf[3] = byte(t >> 8)
+ buf[4] = byte(t)
+ buf[5] = byte(pk.PubKeyAlgo)
+
+ _, err = w.Write(buf[:])
+ if err != nil {
+ return
+ }
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ return writeMPIs(w, pk.n, pk.e)
+ case PubKeyAlgoDSA:
+ return writeMPIs(w, pk.p, pk.q, pk.g, pk.y)
+ case PubKeyAlgoElGamal:
+ return writeMPIs(w, pk.p, pk.g, pk.y)
+ }
+ return errors.InvalidArgumentError("bad public-key algorithm")
+}
+
+// CanSign returns true iff this public key can generate signatures
+func (pk *PublicKey) CanSign() bool {
+ return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal
+}
+
+// VerifySignature returns nil iff sig is a valid signature, made by this
+// public key, of the data hashed into signed. signed is mutated by this call.
+func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) {
+ if !pk.CanSign() {
+ return errors.InvalidArgumentError("public key cannot generate signatures")
+ }
+
+ signed.Write(sig.HashSuffix)
+ hashBytes := signed.Sum(nil)
+
+ if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
+ return errors.SignatureError("hash tag doesn't match")
+ }
+
+ if pk.PubKeyAlgo != sig.PubKeyAlgo {
+ return errors.InvalidArgumentError("public key and signature use different algorithms")
+ }
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey)
+ err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes)
+ if err != nil {
+ return errors.SignatureError("RSA verification failure")
+ }
+ return nil
+ case PubKeyAlgoDSA:
+ dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
+ // Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+ subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
+ if len(hashBytes) > subgroupSize {
+ hashBytes = hashBytes[:subgroupSize]
+ }
+ if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
+ return errors.SignatureError("DSA verification failure")
+ }
+ return nil
+ default:
+ panic("shouldn't happen")
+ }
+ panic("unreachable")
+}
+
+// keySignatureHash returns a Hash of the message that needs to be signed for
+// pk to assert a subkey relationship to signed.
+func keySignatureHash(pk, signed *PublicKey, sig *Signature) (h hash.Hash, err error) {
+ h = sig.Hash.New()
+ if h == nil {
+ return nil, errors.UnsupportedError("hash function")
+ }
+
+ // RFC 4880, section 5.2.4
+ pk.SerializeSignaturePrefix(h)
+ pk.serializeWithoutHeaders(h)
+ signed.SerializeSignaturePrefix(h)
+ signed.serializeWithoutHeaders(h)
+ return
+}
+
+// VerifyKeySignature returns nil iff sig is a valid signature, made by this
+// public key, of signed.
+func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err error) {
+ h, err := keySignatureHash(pk, signed, sig)
+ if err != nil {
+ return err
+ }
+ return pk.VerifySignature(h, sig)
+}
+
+// userIdSignatureHash returns a Hash of the message that needs to be signed
+// to assert that pk is a valid key for id.
+func userIdSignatureHash(id string, pk *PublicKey, sig *Signature) (h hash.Hash, err error) {
+ h = sig.Hash.New()
+ if h == nil {
+ return nil, errors.UnsupportedError("hash function")
+ }
+
+ // RFC 4880, section 5.2.4
+ pk.SerializeSignaturePrefix(h)
+ pk.serializeWithoutHeaders(h)
+
+ var buf [5]byte
+ buf[0] = 0xb4
+ buf[1] = byte(len(id) >> 24)
+ buf[2] = byte(len(id) >> 16)
+ buf[3] = byte(len(id) >> 8)
+ buf[4] = byte(len(id))
+ h.Write(buf[:])
+ h.Write([]byte(id))
+
+ return
+}
+
+// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this
+// public key, of id.
+func (pk *PublicKey) VerifyUserIdSignature(id string, sig *Signature) (err error) {
+ h, err := userIdSignatureHash(id, pk, sig)
+ if err != nil {
+ return err
+ }
+ return pk.VerifySignature(h, sig)
+}
+
+// KeyIdString returns the public key's fingerprint in capital hex
+// (e.g. "6C7EE1B8621CC013").
+func (pk *PublicKey) KeyIdString() string {
+ return fmt.Sprintf("%X", pk.Fingerprint[12:20])
+}
+
+// KeyIdShortString returns the short form of public key's fingerprint
+// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
+func (pk *PublicKey) KeyIdShortString() string {
+ return fmt.Sprintf("%X", pk.Fingerprint[16:20])
+}
+
+// A parsedMPI is used to store the contents of a big integer, along with the
+// bit length that was specified in the original input. This allows the MPI to
+// be reserialized exactly.
+type parsedMPI struct {
+ bytes []byte
+ bitLength uint16
+}
+
+// writeMPIs is a utility function for serializing several big integers to the
+// given Writer.
+func writeMPIs(w io.Writer, mpis ...parsedMPI) (err error) {
+ for _, mpi := range mpis {
+ err = writeMPI(w, mpi.bitLength, mpi.bytes)
+ if err != nil {
+ return
+ }
+ }
+ return
+}
diff --git a/openpgp/packet/public_key_test.go b/openpgp/packet/public_key_test.go
new file mode 100644
index 0000000..72f459f
--- /dev/null
+++ b/openpgp/packet/public_key_test.go
@@ -0,0 +1,99 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+ "time"
+)
+
+var pubKeyTests = []struct {
+ hexData string
+ hexFingerprint string
+ creationTime time.Time
+ pubKeyAlgo PublicKeyAlgorithm
+ keyId uint64
+ keyIdString string
+ keyIdShort string
+}{
+ {rsaPkDataHex, rsaFingerprintHex, time.Unix(0x4d3c5c10, 0), PubKeyAlgoRSA, 0xa34d7e18c20c31bb, "A34D7E18C20C31BB", "C20C31BB"},
+ {dsaPkDataHex, dsaFingerprintHex, time.Unix(0x4d432f89, 0), PubKeyAlgoDSA, 0x8e8fbe54062f19ed, "8E8FBE54062F19ED", "062F19ED"},
+}
+
+func TestPublicKeyRead(t *testing.T) {
+ for i, test := range pubKeyTests {
+ packet, err := Read(readerFromHex(test.hexData))
+ if err != nil {
+ t.Errorf("#%d: Read error: %s", i, err)
+ continue
+ }
+ pk, ok := packet.(*PublicKey)
+ if !ok {
+ t.Errorf("#%d: failed to parse, got: %#v", i, packet)
+ continue
+ }
+ if pk.PubKeyAlgo != test.pubKeyAlgo {
+ t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo)
+ }
+ if !pk.CreationTime.Equal(test.creationTime) {
+ t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime)
+ }
+ expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint)
+ if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) {
+ t.Errorf("#%d: bad fingerprint got:%x want:%x", i, pk.Fingerprint[:], expectedFingerprint)
+ }
+ if pk.KeyId != test.keyId {
+ t.Errorf("#%d: bad keyid got:%x want:%x", i, pk.KeyId, test.keyId)
+ }
+ if g, e := pk.KeyIdString(), test.keyIdString; g != e {
+ t.Errorf("#%d: bad KeyIdString got:%q want:%q", i, g, e)
+ }
+ if g, e := pk.KeyIdShortString(), test.keyIdShort; g != e {
+ t.Errorf("#%d: bad KeyIdShortString got:%q want:%q", i, g, e)
+ }
+ }
+}
+
+func TestPublicKeySerialize(t *testing.T) {
+ for i, test := range pubKeyTests {
+ packet, err := Read(readerFromHex(test.hexData))
+ if err != nil {
+ t.Errorf("#%d: Read error: %s", i, err)
+ continue
+ }
+ pk, ok := packet.(*PublicKey)
+ if !ok {
+ t.Errorf("#%d: failed to parse, got: %#v", i, packet)
+ continue
+ }
+ serializeBuf := bytes.NewBuffer(nil)
+ err = pk.Serialize(serializeBuf)
+ if err != nil {
+ t.Errorf("#%d: failed to serialize: %s", i, err)
+ continue
+ }
+
+ packet, err = Read(serializeBuf)
+ if err != nil {
+ t.Errorf("#%d: Read error (from serialized data): %s", i, err)
+ continue
+ }
+ pk, ok = packet.(*PublicKey)
+ if !ok {
+ t.Errorf("#%d: failed to parse serialized data, got: %#v", i, packet)
+ continue
+ }
+ }
+}
+
+const rsaFingerprintHex = "5fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb"
+
+const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001"
+
+const dsaFingerprintHex = "eece4c094db002103714c63c8e8fbe54062f19ed"
+
+const dsaPkDataHex = "9901a2044d432f89110400cd581334f0d7a1e1bdc8b9d6d8c0baf68793632735d2bb0903224cbaa1dfbf35a60ee7a13b92643421e1eb41aa8d79bea19a115a677f6b8ba3c7818ce53a6c2a24a1608bd8b8d6e55c5090cbde09dd26e356267465ae25e69ec8bdd57c7bbb2623e4d73336f73a0a9098f7f16da2e25252130fd694c0e8070c55a812a423ae7f00a0ebf50e70c2f19c3520a551bd4b08d30f23530d3d03ff7d0bf4a53a64a09dc5e6e6e35854b7d70c882b0c60293401958b1bd9e40abec3ea05ba87cf64899299d4bd6aa7f459c201d3fbbd6c82004bdc5e8a9eb8082d12054cc90fa9d4ec251a843236a588bf49552441817436c4f43326966fe85447d4e6d0acf8fa1ef0f014730770603ad7634c3088dc52501c237328417c31c89ed70400b2f1a98b0bf42f11fefc430704bebbaa41d9f355600c3facee1e490f64208e0e094ea55e3a598a219a58500bf78ac677b670a14f4e47e9cf8eab4f368cc1ddcaa18cc59309d4cc62dd4f680e73e6cc3e1ce87a84d0925efbcb26c575c093fc42eecf45135fabf6403a25c2016e1774c0484e440a18319072c617cc97ac0a3bb0"
diff --git a/openpgp/packet/reader.go b/openpgp/packet/reader.go
new file mode 100644
index 0000000..3325d63
--- /dev/null
+++ b/openpgp/packet/reader.go
@@ -0,0 +1,62 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "io"
+)
+
+// Reader reads packets from an io.Reader and allows packets to be 'unread' so
+// that they result from the next call to Next.
+type Reader struct {
+ q []Packet
+ readers []io.Reader
+}
+
+// Next returns the most recently unread Packet, or reads another packet from
+// the top-most io.Reader. Unknown packet types are skipped.
+func (r *Reader) Next() (p Packet, err error) {
+ if len(r.q) > 0 {
+ p = r.q[len(r.q)-1]
+ r.q = r.q[:len(r.q)-1]
+ return
+ }
+
+ for len(r.readers) > 0 {
+ p, err = Read(r.readers[len(r.readers)-1])
+ if err == nil {
+ return
+ }
+ if err == io.EOF {
+ r.readers = r.readers[:len(r.readers)-1]
+ continue
+ }
+ if _, ok := err.(errors.UnknownPacketTypeError); !ok {
+ return nil, err
+ }
+ }
+
+ return nil, io.EOF
+}
+
+// Push causes the Reader to start reading from a new io.Reader. When an EOF
+// error is seen from the new io.Reader, it is popped and the Reader continues
+// to read from the next most recent io.Reader.
+func (r *Reader) Push(reader io.Reader) {
+ r.readers = append(r.readers, reader)
+}
+
+// Unread causes the given Packet to be returned from the next call to Next.
+func (r *Reader) Unread(p Packet) {
+ r.q = append(r.q, p)
+}
+
+func NewReader(r io.Reader) *Reader {
+ return &Reader{
+ q: nil,
+ readers: []io.Reader{r},
+ }
+}
diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go
new file mode 100644
index 0000000..7f2fdab
--- /dev/null
+++ b/openpgp/packet/signature.go
@@ -0,0 +1,611 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "code.google.com/p/go.crypto/openpgp/s2k"
+ "crypto"
+ "crypto/dsa"
+ "crypto/rsa"
+ "encoding/binary"
+ "hash"
+ "io"
+ "strconv"
+ "time"
+)
+
+// Signature represents a signature. See RFC 4880, section 5.2.
+type Signature struct {
+ SigType SignatureType
+ PubKeyAlgo PublicKeyAlgorithm
+ Hash crypto.Hash
+
+ // HashSuffix is extra data that is hashed in after the signed data.
+ HashSuffix []byte
+ // HashTag contains the first two bytes of the hash for fast rejection
+ // of bad signed data.
+ HashTag [2]byte
+ CreationTime time.Time
+
+ RSASignature parsedMPI
+ DSASigR, DSASigS parsedMPI
+
+ // rawSubpackets contains the unparsed subpackets, in order.
+ rawSubpackets []outputSubpacket
+
+ // The following are optional so are nil when not included in the
+ // signature.
+
+ SigLifetimeSecs, KeyLifetimeSecs *uint32
+ PreferredSymmetric, PreferredHash, PreferredCompression []uint8
+ IssuerKeyId *uint64
+ IsPrimaryId *bool
+
+ // FlagsValid is set if any flags were given. See RFC 4880, section
+ // 5.2.3.21 for details.
+ FlagsValid bool
+ FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage bool
+
+ outSubpackets []outputSubpacket
+}
+
+func (sig *Signature) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.2.3
+ var buf [5]byte
+ _, err = readFull(r, buf[:1])
+ if err != nil {
+ return
+ }
+ if buf[0] != 4 {
+ err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
+ return
+ }
+
+ _, err = readFull(r, buf[:5])
+ if err != nil {
+ return
+ }
+ sig.SigType = SignatureType(buf[0])
+ sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1])
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
+ default:
+ err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
+ return
+ }
+
+ var ok bool
+ sig.Hash, ok = s2k.HashIdToHash(buf[2])
+ if !ok {
+ return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
+ }
+
+ hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4])
+ l := 6 + hashedSubpacketsLength
+ sig.HashSuffix = make([]byte, l+6)
+ sig.HashSuffix[0] = 4
+ copy(sig.HashSuffix[1:], buf[:5])
+ hashedSubpackets := sig.HashSuffix[6:l]
+ _, err = readFull(r, hashedSubpackets)
+ if err != nil {
+ return
+ }
+ // See RFC 4880, section 5.2.4
+ trailer := sig.HashSuffix[l:]
+ trailer[0] = 4
+ trailer[1] = 0xff
+ trailer[2] = uint8(l >> 24)
+ trailer[3] = uint8(l >> 16)
+ trailer[4] = uint8(l >> 8)
+ trailer[5] = uint8(l)
+
+ err = parseSignatureSubpackets(sig, hashedSubpackets, true)
+ if err != nil {
+ return
+ }
+
+ _, err = readFull(r, buf[:2])
+ if err != nil {
+ return
+ }
+ unhashedSubpacketsLength := int(buf[0])<<8 | int(buf[1])
+ unhashedSubpackets := make([]byte, unhashedSubpacketsLength)
+ _, err = readFull(r, unhashedSubpackets)
+ if err != nil {
+ return
+ }
+ err = parseSignatureSubpackets(sig, unhashedSubpackets, false)
+ if err != nil {
+ return
+ }
+
+ _, err = readFull(r, sig.HashTag[:2])
+ if err != nil {
+ return
+ }
+
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
+ case PubKeyAlgoDSA:
+ sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r)
+ if err == nil {
+ sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
+ }
+ default:
+ panic("unreachable")
+ }
+ return
+}
+
+// parseSignatureSubpackets parses subpackets of the main signature packet. See
+// RFC 4880, section 5.2.3.1.
+func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) {
+ for len(subpackets) > 0 {
+ subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed)
+ if err != nil {
+ return
+ }
+ }
+
+ if sig.CreationTime.IsZero() {
+ err = errors.StructuralError("no creation time in signature")
+ }
+
+ return
+}
+
+type signatureSubpacketType uint8
+
+const (
+ creationTimeSubpacket signatureSubpacketType = 2
+ signatureExpirationSubpacket signatureSubpacketType = 3
+ keyExpirationSubpacket signatureSubpacketType = 9
+ prefSymmetricAlgosSubpacket signatureSubpacketType = 11
+ issuerSubpacket signatureSubpacketType = 16
+ prefHashAlgosSubpacket signatureSubpacketType = 21
+ prefCompressionSubpacket signatureSubpacketType = 22
+ primaryUserIdSubpacket signatureSubpacketType = 25
+ keyFlagsSubpacket signatureSubpacketType = 27
+)
+
+// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
+func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) {
+ // RFC 4880, section 5.2.3.1
+ var (
+ length uint32
+ packetType signatureSubpacketType
+ isCritical bool
+ )
+ switch {
+ case subpacket[0] < 192:
+ length = uint32(subpacket[0])
+ subpacket = subpacket[1:]
+ case subpacket[0] < 255:
+ if len(subpacket) < 2 {
+ goto Truncated
+ }
+ length = uint32(subpacket[0]-192)<<8 + uint32(subpacket[1]) + 192
+ subpacket = subpacket[2:]
+ default:
+ if len(subpacket) < 5 {
+ goto Truncated
+ }
+ length = uint32(subpacket[1])<<24 |
+ uint32(subpacket[2])<<16 |
+ uint32(subpacket[3])<<8 |
+ uint32(subpacket[4])
+ subpacket = subpacket[5:]
+ }
+ if length > uint32(len(subpacket)) {
+ goto Truncated
+ }
+ rest = subpacket[length:]
+ subpacket = subpacket[:length]
+ if len(subpacket) == 0 {
+ err = errors.StructuralError("zero length signature subpacket")
+ return
+ }
+ packetType = signatureSubpacketType(subpacket[0] & 0x7f)
+ isCritical = subpacket[0]&0x80 == 0x80
+ subpacket = subpacket[1:]
+ sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
+ switch packetType {
+ case creationTimeSubpacket:
+ if !isHashed {
+ err = errors.StructuralError("signature creation time in non-hashed area")
+ return
+ }
+ if len(subpacket) != 4 {
+ err = errors.StructuralError("signature creation time not four bytes")
+ return
+ }
+ t := binary.BigEndian.Uint32(subpacket)
+ sig.CreationTime = time.Unix(int64(t), 0)
+ case signatureExpirationSubpacket:
+ // Signature expiration time, section 5.2.3.10
+ if !isHashed {
+ return
+ }
+ if len(subpacket) != 4 {
+ err = errors.StructuralError("expiration subpacket with bad length")
+ return
+ }
+ sig.SigLifetimeSecs = new(uint32)
+ *sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
+ case keyExpirationSubpacket:
+ // Key expiration time, section 5.2.3.6
+ if !isHashed {
+ return
+ }
+ if len(subpacket) != 4 {
+ err = errors.StructuralError("key expiration subpacket with bad length")
+ return
+ }
+ sig.KeyLifetimeSecs = new(uint32)
+ *sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
+ case prefSymmetricAlgosSubpacket:
+ // Preferred symmetric algorithms, section 5.2.3.7
+ if !isHashed {
+ return
+ }
+ sig.PreferredSymmetric = make([]byte, len(subpacket))
+ copy(sig.PreferredSymmetric, subpacket)
+ case issuerSubpacket:
+ // Issuer, section 5.2.3.5
+ if len(subpacket) != 8 {
+ err = errors.StructuralError("issuer subpacket with bad length")
+ return
+ }
+ sig.IssuerKeyId = new(uint64)
+ *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
+ case prefHashAlgosSubpacket:
+ // Preferred hash algorithms, section 5.2.3.8
+ if !isHashed {
+ return
+ }
+ sig.PreferredHash = make([]byte, len(subpacket))
+ copy(sig.PreferredHash, subpacket)
+ case prefCompressionSubpacket:
+ // Preferred compression algorithms, section 5.2.3.9
+ if !isHashed {
+ return
+ }
+ sig.PreferredCompression = make([]byte, len(subpacket))
+ copy(sig.PreferredCompression, subpacket)
+ case primaryUserIdSubpacket:
+ // Primary User ID, section 5.2.3.19
+ if !isHashed {
+ return
+ }
+ if len(subpacket) != 1 {
+ err = errors.StructuralError("primary user id subpacket with bad length")
+ return
+ }
+ sig.IsPrimaryId = new(bool)
+ if subpacket[0] > 0 {
+ *sig.IsPrimaryId = true
+ }
+ case keyFlagsSubpacket:
+ // Key flags, section 5.2.3.21
+ if !isHashed {
+ return
+ }
+ if len(subpacket) == 0 {
+ err = errors.StructuralError("empty key flags subpacket")
+ return
+ }
+ sig.FlagsValid = true
+ if subpacket[0]&1 != 0 {
+ sig.FlagCertify = true
+ }
+ if subpacket[0]&2 != 0 {
+ sig.FlagSign = true
+ }
+ if subpacket[0]&4 != 0 {
+ sig.FlagEncryptCommunications = true
+ }
+ if subpacket[0]&8 != 0 {
+ sig.FlagEncryptStorage = true
+ }
+
+ default:
+ if isCritical {
+ err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
+ return
+ }
+ }
+ return
+
+Truncated:
+ err = errors.StructuralError("signature subpacket truncated")
+ return
+}
+
+// subpacketLengthLength returns the length, in bytes, of an encoded length value.
+func subpacketLengthLength(length int) int {
+ if length < 192 {
+ return 1
+ }
+ if length < 16320 {
+ return 2
+ }
+ return 5
+}
+
+// serializeSubpacketLength marshals the given length into to.
+func serializeSubpacketLength(to []byte, length int) int {
+ if length < 192 {
+ to[0] = byte(length)
+ return 1
+ }
+ if length < 16320 {
+ length -= 192
+ to[0] = byte(length >> 8)
+ to[1] = byte(length)
+ return 2
+ }
+ to[0] = 255
+ to[1] = byte(length >> 24)
+ to[2] = byte(length >> 16)
+ to[3] = byte(length >> 8)
+ to[4] = byte(length)
+ return 5
+}
+
+// subpacketsLength returns the serialized length, in bytes, of the given
+// subpackets.
+func subpacketsLength(subpackets []outputSubpacket, hashed bool) (length int) {
+ for _, subpacket := range subpackets {
+ if subpacket.hashed == hashed {
+ length += subpacketLengthLength(len(subpacket.contents) + 1)
+ length += 1 // type byte
+ length += len(subpacket.contents)
+ }
+ }
+ return
+}
+
+// serializeSubpackets marshals the given subpackets into to.
+func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) {
+ for _, subpacket := range subpackets {
+ if subpacket.hashed == hashed {
+ n := serializeSubpacketLength(to, len(subpacket.contents)+1)
+ to[n] = byte(subpacket.subpacketType)
+ to = to[1+n:]
+ n = copy(to, subpacket.contents)
+ to = to[n:]
+ }
+ }
+ return
+}
+
+// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing.
+func (sig *Signature) buildHashSuffix() (err error) {
+ hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true)
+
+ var ok bool
+ l := 6 + hashedSubpacketsLen
+ sig.HashSuffix = make([]byte, l+6)
+ sig.HashSuffix[0] = 4
+ sig.HashSuffix[1] = uint8(sig.SigType)
+ sig.HashSuffix[2] = uint8(sig.PubKeyAlgo)
+ sig.HashSuffix[3], ok = s2k.HashToHashId(sig.Hash)
+ if !ok {
+ sig.HashSuffix = nil
+ return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
+ }
+ sig.HashSuffix[4] = byte(hashedSubpacketsLen >> 8)
+ sig.HashSuffix[5] = byte(hashedSubpacketsLen)
+ serializeSubpackets(sig.HashSuffix[6:l], sig.outSubpackets, true)
+ trailer := sig.HashSuffix[l:]
+ trailer[0] = 4
+ trailer[1] = 0xff
+ trailer[2] = byte(l >> 24)
+ trailer[3] = byte(l >> 16)
+ trailer[4] = byte(l >> 8)
+ trailer[5] = byte(l)
+ return
+}
+
+func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) {
+ err = sig.buildHashSuffix()
+ if err != nil {
+ return
+ }
+
+ h.Write(sig.HashSuffix)
+ digest = h.Sum(nil)
+ copy(sig.HashTag[:], digest)
+ return
+}
+
+// Sign signs a message with a private key. The hash, h, must contain
+// the hash of the message to be signed and will be mutated by this function.
+// On success, the signature is stored in sig. Call Serialize to write it out.
+func (sig *Signature) Sign(rand io.Reader, h hash.Hash, priv *PrivateKey) (err error) {
+ sig.outSubpackets = sig.buildSubpackets()
+ digest, err := sig.signPrepareHash(h)
+ if err != nil {
+ return
+ }
+
+ switch priv.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)
+ sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))
+ case PubKeyAlgoDSA:
+ dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
+
+ // Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+ subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8
+ if len(digest) > subgroupSize {
+ digest = digest[:subgroupSize]
+ }
+ r, s, err := dsa.Sign(rand, dsaPriv, digest)
+ if err == nil {
+ sig.DSASigR.bytes = r.Bytes()
+ sig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes))
+ sig.DSASigS.bytes = s.Bytes()
+ sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
+ }
+ default:
+ err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
+ }
+
+ return
+}
+
+// SignUserId computes a signature from priv, asserting that pub is a valid
+// key for the identity id. On success, the signature is stored in sig. Call
+// Serialize to write it out.
+func (sig *Signature) SignUserId(rand io.Reader, id string, pub *PublicKey, priv *PrivateKey) error {
+ h, err := userIdSignatureHash(id, pub, sig)
+ if err != nil {
+ return nil
+ }
+ return sig.Sign(rand, h, priv)
+}
+
+// SignKey computes a signature from priv, asserting that pub is a subkey. On
+// success, the signature is stored in sig. Call Serialize to write it out.
+func (sig *Signature) SignKey(rand io.Reader, pub *PublicKey, priv *PrivateKey) error {
+ h, err := keySignatureHash(&priv.PublicKey, pub, sig)
+ if err != nil {
+ return err
+ }
+ return sig.Sign(rand, h, priv)
+}
+
+// Serialize marshals sig to w. SignRSA or SignDSA must have been called first.
+func (sig *Signature) Serialize(w io.Writer) (err error) {
+ if len(sig.outSubpackets) == 0 {
+ sig.outSubpackets = sig.rawSubpackets
+ }
+ if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil {
+ return errors.InvalidArgumentError("Signature: need to call SignRSA or SignDSA before Serialize")
+ }
+
+ sigLength := 0
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ sigLength = 2 + len(sig.RSASignature.bytes)
+ case PubKeyAlgoDSA:
+ sigLength = 2 + len(sig.DSASigR.bytes)
+ sigLength += 2 + len(sig.DSASigS.bytes)
+ default:
+ panic("impossible")
+ }
+
+ unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false)
+ length := len(sig.HashSuffix) - 6 /* trailer not included */ +
+ 2 /* length of unhashed subpackets */ + unhashedSubpacketsLen +
+ 2 /* hash tag */ + sigLength
+ err = serializeHeader(w, packetTypeSignature, length)
+ if err != nil {
+ return
+ }
+
+ _, err = w.Write(sig.HashSuffix[:len(sig.HashSuffix)-6])
+ if err != nil {
+ return
+ }
+
+ unhashedSubpackets := make([]byte, 2+unhashedSubpacketsLen)
+ unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8)
+ unhashedSubpackets[1] = byte(unhashedSubpacketsLen)
+ serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false)
+
+ _, err = w.Write(unhashedSubpackets)
+ if err != nil {
+ return
+ }
+ _, err = w.Write(sig.HashTag[:])
+ if err != nil {
+ return
+ }
+
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ err = writeMPIs(w, sig.RSASignature)
+ case PubKeyAlgoDSA:
+ err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
+ default:
+ panic("impossible")
+ }
+ return
+}
+
+// outputSubpacket represents a subpacket to be marshaled.
+type outputSubpacket struct {
+ hashed bool // true if this subpacket is in the hashed area.
+ subpacketType signatureSubpacketType
+ isCritical bool
+ contents []byte
+}
+
+func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) {
+ creationTime := make([]byte, 4)
+ binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix()))
+ subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, false, creationTime})
+
+ if sig.IssuerKeyId != nil {
+ keyId := make([]byte, 8)
+ binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
+ subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
+ }
+
+ if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
+ sigLifetime := make([]byte, 4)
+ binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
+ subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime})
+ }
+
+ // Key flags may only appear in self-signatures or certification signatures.
+
+ if sig.FlagsValid {
+ var flags byte
+ if sig.FlagCertify {
+ flags |= 1
+ }
+ if sig.FlagSign {
+ flags |= 2
+ }
+ if sig.FlagEncryptCommunications {
+ flags |= 4
+ }
+ if sig.FlagEncryptStorage {
+ flags |= 8
+ }
+ subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
+ }
+
+ // The following subpackets may only appear in self-signatures
+
+ if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
+ keyLifetime := make([]byte, 4)
+ binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
+ subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime})
+ }
+
+ if sig.IsPrimaryId != nil && *sig.IsPrimaryId {
+ subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}})
+ }
+
+ if len(sig.PreferredSymmetric) > 0 {
+ subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric})
+ }
+
+ if len(sig.PreferredHash) > 0 {
+ subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash})
+ }
+
+ if len(sig.PreferredCompression) > 0 {
+ subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression})
+ }
+
+ return
+}
diff --git a/openpgp/packet/signature_test.go b/openpgp/packet/signature_test.go
new file mode 100644
index 0000000..c1bbde8
--- /dev/null
+++ b/openpgp/packet/signature_test.go
@@ -0,0 +1,42 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "crypto"
+ "encoding/hex"
+ "testing"
+)
+
+func TestSignatureRead(t *testing.T) {
+ packet, err := Read(readerFromHex(signatureDataHex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ sig, ok := packet.(*Signature)
+ if !ok || sig.SigType != SigTypeBinary || sig.PubKeyAlgo != PubKeyAlgoRSA || sig.Hash != crypto.SHA1 {
+ t.Errorf("failed to parse, got: %#v", packet)
+ }
+}
+
+func TestSignatureReserialize(t *testing.T) {
+ packet, _ := Read(readerFromHex(signatureDataHex))
+ sig := packet.(*Signature)
+ out := new(bytes.Buffer)
+ err := sig.Serialize(out)
+ if err != nil {
+ t.Errorf("error reserializing: %s", err)
+ return
+ }
+
+ expected, _ := hex.DecodeString(signatureDataHex)
+ if !bytes.Equal(expected, out.Bytes()) {
+ t.Errorf("output doesn't match input (got vs expected):\n%s\n%s", hex.Dump(out.Bytes()), hex.Dump(expected))
+ }
+}
+
+const signatureDataHex = "c2c05c04000102000605024cb45112000a0910ab105c91af38fb158f8d07ff5596ea368c5efe015bed6e78348c0f033c931d5f2ce5db54ce7f2a7e4b4ad64db758d65a7a71773edeab7ba2a9e0908e6a94a1175edd86c1d843279f045b021a6971a72702fcbd650efc393c5474d5b59a15f96d2eaad4c4c426797e0dcca2803ef41c6ff234d403eec38f31d610c344c06f2401c262f0993b2e66cad8a81ebc4322c723e0d4ba09fe917e8777658307ad8329adacba821420741009dfe87f007759f0982275d028a392c6ed983a0d846f890b36148c7358bdb8a516007fac760261ecd06076813831a36d0459075d1befa245ae7f7fb103d92ca759e9498fe60ef8078a39a3beda510deea251ea9f0a7f0df6ef42060f20780360686f3e400e"
diff --git a/openpgp/packet/symmetric_key_encrypted.go b/openpgp/packet/symmetric_key_encrypted.go
new file mode 100644
index 0000000..574badd
--- /dev/null
+++ b/openpgp/packet/symmetric_key_encrypted.go
@@ -0,0 +1,161 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "code.google.com/p/go.crypto/openpgp/s2k"
+ "crypto/cipher"
+ "io"
+ "strconv"
+)
+
+// This is the largest session key that we'll support. Since no 512-bit cipher
+// has even been seriously used, this is comfortably large.
+const maxSessionKeySizeInBytes = 64
+
+// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
+// 4880, section 5.3.
+type SymmetricKeyEncrypted struct {
+ CipherFunc CipherFunction
+ Encrypted bool
+ Key []byte // Empty unless Encrypted is false.
+ s2k func(out, in []byte)
+ encryptedKey []byte
+}
+
+const symmetricKeyEncryptedVersion = 4
+
+func (ske *SymmetricKeyEncrypted) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.3.
+ var buf [2]byte
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ if buf[0] != symmetricKeyEncryptedVersion {
+ return errors.UnsupportedError("SymmetricKeyEncrypted version")
+ }
+ ske.CipherFunc = CipherFunction(buf[1])
+
+ if ske.CipherFunc.KeySize() == 0 {
+ return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
+ }
+
+ ske.s2k, err = s2k.Parse(r)
+ if err != nil {
+ return
+ }
+
+ encryptedKey := make([]byte, maxSessionKeySizeInBytes)
+ // The session key may follow. We just have to try and read to find
+ // out. If it exists then we limit it to maxSessionKeySizeInBytes.
+ n, err := readFull(r, encryptedKey)
+ if err != nil && err != io.ErrUnexpectedEOF {
+ return
+ }
+ err = nil
+ if n != 0 {
+ if n == maxSessionKeySizeInBytes {
+ return errors.UnsupportedError("oversized encrypted session key")
+ }
+ ske.encryptedKey = encryptedKey[:n]
+ }
+
+ ske.Encrypted = true
+
+ return
+}
+
+// Decrypt attempts to decrypt an encrypted session key. If it returns nil,
+// ske.Key will contain the session key.
+func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) error {
+ if !ske.Encrypted {
+ return nil
+ }
+
+ key := make([]byte, ske.CipherFunc.KeySize())
+ ske.s2k(key, passphrase)
+
+ if len(ske.encryptedKey) == 0 {
+ ske.Key = key
+ } else {
+ // the IV is all zeros
+ iv := make([]byte, ske.CipherFunc.blockSize())
+ c := cipher.NewCFBDecrypter(ske.CipherFunc.new(key), iv)
+ c.XORKeyStream(ske.encryptedKey, ske.encryptedKey)
+ ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
+ if ske.CipherFunc.blockSize() == 0 {
+ return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(ske.CipherFunc)))
+ }
+ ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
+ ske.Key = ske.encryptedKey[1:]
+ if len(ske.Key)%ske.CipherFunc.blockSize() != 0 {
+ ske.Key = nil
+ return errors.StructuralError("length of decrypted key not a multiple of block size")
+ }
+ }
+
+ ske.Encrypted = false
+ return nil
+}
+
+// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w. The
+// packet contains a random session key, encrypted by a key derived from the
+// given passphrase. The session key is returned and must be passed to
+// SerializeSymmetricallyEncrypted.
+func SerializeSymmetricKeyEncrypted(w io.Writer, rand io.Reader, passphrase []byte, cipherFunc CipherFunction) (key []byte, err error) {
+ keySize := cipherFunc.KeySize()
+ if keySize == 0 {
+ return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
+ }
+
+ s2kBuf := new(bytes.Buffer)
+ keyEncryptingKey := make([]byte, keySize)
+ // s2k.Serialize salts and stretches the passphrase, and writes the
+ // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
+ err = s2k.Serialize(s2kBuf, keyEncryptingKey, rand, passphrase)
+ if err != nil {
+ return
+ }
+ s2kBytes := s2kBuf.Bytes()
+
+ packetLength := 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
+ err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
+ if err != nil {
+ return
+ }
+
+ var buf [2]byte
+ buf[0] = symmetricKeyEncryptedVersion
+ buf[1] = byte(cipherFunc)
+ _, err = w.Write(buf[:])
+ if err != nil {
+ return
+ }
+ _, err = w.Write(s2kBytes)
+ if err != nil {
+ return
+ }
+
+ sessionKey := make([]byte, keySize)
+ _, err = io.ReadFull(rand, sessionKey)
+ if err != nil {
+ return
+ }
+ iv := make([]byte, cipherFunc.blockSize())
+ c := cipher.NewCFBEncrypter(cipherFunc.new(keyEncryptingKey), iv)
+ encryptedCipherAndKey := make([]byte, keySize+1)
+ c.XORKeyStream(encryptedCipherAndKey, buf[1:])
+ c.XORKeyStream(encryptedCipherAndKey[1:], sessionKey)
+ _, err = w.Write(encryptedCipherAndKey)
+ if err != nil {
+ return
+ }
+
+ key = sessionKey
+ return
+}
diff --git a/openpgp/packet/symmetric_key_encrypted_test.go b/openpgp/packet/symmetric_key_encrypted_test.go
new file mode 100644
index 0000000..87690f0
--- /dev/null
+++ b/openpgp/packet/symmetric_key_encrypted_test.go
@@ -0,0 +1,101 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "io"
+ "io/ioutil"
+ "testing"
+)
+
+func TestSymmetricKeyEncrypted(t *testing.T) {
+ buf := readerFromHex(symmetricallyEncryptedHex)
+ packet, err := Read(buf)
+ if err != nil {
+ t.Errorf("failed to read SymmetricKeyEncrypted: %s", err)
+ return
+ }
+ ske, ok := packet.(*SymmetricKeyEncrypted)
+ if !ok {
+ t.Error("didn't find SymmetricKeyEncrypted packet")
+ return
+ }
+ err = ske.Decrypt([]byte("password"))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ packet, err = Read(buf)
+ if err != nil {
+ t.Errorf("failed to read SymmetricallyEncrypted: %s", err)
+ return
+ }
+ se, ok := packet.(*SymmetricallyEncrypted)
+ if !ok {
+ t.Error("didn't find SymmetricallyEncrypted packet")
+ return
+ }
+ r, err := se.Decrypt(ske.CipherFunc, ske.Key)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ contents, err := ioutil.ReadAll(r)
+ if err != nil && err != io.EOF {
+ t.Error(err)
+ return
+ }
+
+ expectedContents, _ := hex.DecodeString(symmetricallyEncryptedContentsHex)
+ if !bytes.Equal(expectedContents, contents) {
+ t.Errorf("bad contents got:%x want:%x", contents, expectedContents)
+ }
+}
+
+const symmetricallyEncryptedHex = "8c0d04030302371a0b38d884f02060c91cf97c9973b8e58e028e9501708ccfe618fb92afef7fa2d80ddadd93cf"
+const symmetricallyEncryptedContentsHex = "cb1062004d14c4df636f6e74656e74732e0a"
+
+func TestSerializeSymmetricKeyEncrypted(t *testing.T) {
+ buf := bytes.NewBuffer(nil)
+ passphrase := []byte("testing")
+ cipherFunc := CipherAES128
+
+ key, err := SerializeSymmetricKeyEncrypted(buf, rand.Reader, passphrase, cipherFunc)
+ if err != nil {
+ t.Errorf("failed to serialize: %s", err)
+ return
+ }
+
+ p, err := Read(buf)
+ if err != nil {
+ t.Errorf("failed to reparse: %s", err)
+ return
+ }
+ ske, ok := p.(*SymmetricKeyEncrypted)
+ if !ok {
+ t.Errorf("parsed a different packet type: %#v", p)
+ return
+ }
+
+ if !ske.Encrypted {
+ t.Errorf("SKE not encrypted but should be")
+ }
+ if ske.CipherFunc != cipherFunc {
+ t.Errorf("SKE cipher function is %d (expected %d)", ske.CipherFunc, cipherFunc)
+ }
+ err = ske.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("failed to decrypt reparsed SKE: %s", err)
+ return
+ }
+ if !bytes.Equal(key, ske.Key) {
+ t.Errorf("keys don't match after Decrpyt: %x (original) vs %x (parsed)", key, ske.Key)
+ }
+}
diff --git a/openpgp/packet/symmetrically_encrypted.go b/openpgp/packet/symmetrically_encrypted.go
new file mode 100644
index 0000000..a420768
--- /dev/null
+++ b/openpgp/packet/symmetrically_encrypted.go
@@ -0,0 +1,289 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "crypto/cipher"
+ "crypto/sha1"
+ "crypto/subtle"
+ "hash"
+ "io"
+ "strconv"
+)
+
+// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
+// encrypted contents will consist of more OpenPGP packets. See RFC 4880,
+// sections 5.7 and 5.13.
+type SymmetricallyEncrypted struct {
+ MDC bool // true iff this is a type 18 packet and thus has an embedded MAC.
+ contents io.Reader
+ prefix []byte
+}
+
+const symmetricallyEncryptedVersion = 1
+
+func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
+ if se.MDC {
+ // See RFC 4880, section 5.13.
+ var buf [1]byte
+ _, err := readFull(r, buf[:])
+ if err != nil {
+ return err
+ }
+ if buf[0] != symmetricallyEncryptedVersion {
+ return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
+ }
+ }
+ se.contents = r
+ return nil
+}
+
+// Decrypt returns a ReadCloser, from which the decrypted contents of the
+// packet can be read. An incorrect key can, with high probability, be detected
+// immediately and this will result in a KeyIncorrect error being returned.
+func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
+ keySize := c.KeySize()
+ if keySize == 0 {
+ return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
+ }
+ if len(key) != keySize {
+ return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
+ }
+
+ if se.prefix == nil {
+ se.prefix = make([]byte, c.blockSize()+2)
+ _, err := readFull(se.contents, se.prefix)
+ if err != nil {
+ return nil, err
+ }
+ } else if len(se.prefix) != c.blockSize()+2 {
+ return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
+ }
+
+ ocfbResync := cipher.OCFBResync
+ if se.MDC {
+ // MDC packets use a different form of OCFB mode.
+ ocfbResync = cipher.OCFBNoResync
+ }
+
+ s := cipher.NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
+ if s == nil {
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ plaintext := cipher.StreamReader{S: s, R: se.contents}
+
+ if se.MDC {
+ // MDC packets have an embedded hash that we need to check.
+ h := sha1.New()
+ h.Write(se.prefix)
+ return &seMDCReader{in: plaintext, h: h}, nil
+ }
+
+ // Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
+ return seReader{plaintext}, nil
+}
+
+// seReader wraps an io.Reader with a no-op Close method.
+type seReader struct {
+ in io.Reader
+}
+
+func (ser seReader) Read(buf []byte) (int, error) {
+ return ser.in.Read(buf)
+}
+
+func (ser seReader) Close() error {
+ return nil
+}
+
+const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
+
+// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
+// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
+// MDC packet containing a hash of the previous contents which is checked
+// against the running hash. See RFC 4880, section 5.13.
+type seMDCReader struct {
+ in io.Reader
+ h hash.Hash
+ trailer [mdcTrailerSize]byte
+ scratch [mdcTrailerSize]byte
+ trailerUsed int
+ error bool
+ eof bool
+}
+
+func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
+ if ser.error {
+ err = io.ErrUnexpectedEOF
+ return
+ }
+ if ser.eof {
+ err = io.EOF
+ return
+ }
+
+ // If we haven't yet filled the trailer buffer then we must do that
+ // first.
+ for ser.trailerUsed < mdcTrailerSize {
+ n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
+ ser.trailerUsed += n
+ if err == io.EOF {
+ if ser.trailerUsed != mdcTrailerSize {
+ n = 0
+ err = io.ErrUnexpectedEOF
+ ser.error = true
+ return
+ }
+ ser.eof = true
+ n = 0
+ return
+ }
+
+ if err != nil {
+ n = 0
+ return
+ }
+ }
+
+ // If it's a short read then we read into a temporary buffer and shift
+ // the data into the caller's buffer.
+ if len(buf) <= mdcTrailerSize {
+ n, err = readFull(ser.in, ser.scratch[:len(buf)])
+ copy(buf, ser.trailer[:n])
+ ser.h.Write(buf[:n])
+ copy(ser.trailer[:], ser.trailer[n:])
+ copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
+ if n < len(buf) {
+ ser.eof = true
+ err = io.EOF
+ }
+ return
+ }
+
+ n, err = ser.in.Read(buf[mdcTrailerSize:])
+ copy(buf, ser.trailer[:])
+ ser.h.Write(buf[:n])
+ copy(ser.trailer[:], buf[n:])
+
+ if err == io.EOF {
+ ser.eof = true
+ }
+ return
+}
+
+// This is a new-format packet tag byte for a type 19 (MDC) packet.
+const mdcPacketTagByte = byte(0x80) | 0x40 | 19
+
+func (ser *seMDCReader) Close() error {
+ if ser.error {
+ return errors.SignatureError("error during reading")
+ }
+
+ for !ser.eof {
+ // We haven't seen EOF so we need to read to the end
+ var buf [1024]byte
+ _, err := ser.Read(buf[:])
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return errors.SignatureError("error during reading")
+ }
+ }
+
+ if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
+ return errors.SignatureError("MDC packet not found")
+ }
+ ser.h.Write(ser.trailer[:2])
+
+ final := ser.h.Sum(nil)
+ if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
+ return errors.SignatureError("hash mismatch")
+ }
+ return nil
+}
+
+// An seMDCWriter writes through to an io.WriteCloser while maintains a running
+// hash of the data written. On close, it emits an MDC packet containing the
+// running hash.
+type seMDCWriter struct {
+ w io.WriteCloser
+ h hash.Hash
+}
+
+func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
+ w.h.Write(buf)
+ return w.w.Write(buf)
+}
+
+func (w *seMDCWriter) Close() (err error) {
+ var buf [mdcTrailerSize]byte
+
+ buf[0] = mdcPacketTagByte
+ buf[1] = sha1.Size
+ w.h.Write(buf[:2])
+ digest := w.h.Sum(nil)
+ copy(buf[2:], digest)
+
+ _, err = w.w.Write(buf[:])
+ if err != nil {
+ return
+ }
+ return w.w.Close()
+}
+
+// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
+type noOpCloser struct {
+ w io.Writer
+}
+
+func (c noOpCloser) Write(data []byte) (n int, err error) {
+ return c.w.Write(data)
+}
+
+func (c noOpCloser) Close() error {
+ return nil
+}
+
+// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
+// to w and returns a WriteCloser to which the to-be-encrypted packets can be
+// written.
+func SerializeSymmetricallyEncrypted(w io.Writer, rand io.Reader, c CipherFunction, key []byte) (contents io.WriteCloser, err error) {
+ if c.KeySize() != len(key) {
+ return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
+ }
+ writeCloser := noOpCloser{w}
+ ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC)
+ if err != nil {
+ return
+ }
+
+ _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion})
+ if err != nil {
+ return
+ }
+
+ block := c.new(key)
+ blockSize := block.BlockSize()
+ iv := make([]byte, blockSize)
+ _, err = rand.Read(iv)
+ if err != nil {
+ return
+ }
+ s, prefix := cipher.NewOCFBEncrypter(block, iv, cipher.OCFBNoResync)
+ _, err = ciphertext.Write(prefix)
+ if err != nil {
+ return
+ }
+ plaintext := cipher.StreamWriter{S: s, W: ciphertext}
+
+ h := sha1.New()
+ h.Write(iv)
+ h.Write(iv[blockSize-2:])
+ contents = &seMDCWriter{w: plaintext, h: h}
+ return
+}
diff --git a/openpgp/packet/symmetrically_encrypted_test.go b/openpgp/packet/symmetrically_encrypted_test.go
new file mode 100644
index 0000000..32ccff2
--- /dev/null
+++ b/openpgp/packet/symmetrically_encrypted_test.go
@@ -0,0 +1,124 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "bytes"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "crypto/rand"
+ "crypto/sha1"
+ "encoding/hex"
+ "io"
+ "io/ioutil"
+ "testing"
+)
+
+// TestReader wraps a []byte and returns reads of a specific length.
+type testReader struct {
+ data []byte
+ stride int
+}
+
+func (t *testReader) Read(buf []byte) (n int, err error) {
+ n = t.stride
+ if n > len(t.data) {
+ n = len(t.data)
+ }
+ if n > len(buf) {
+ n = len(buf)
+ }
+ copy(buf, t.data)
+ t.data = t.data[n:]
+ if len(t.data) == 0 {
+ err = io.EOF
+ }
+ return
+}
+
+func testMDCReader(t *testing.T) {
+ mdcPlaintext, _ := hex.DecodeString(mdcPlaintextHex)
+
+ for stride := 1; stride < len(mdcPlaintext)/2; stride++ {
+ r := &testReader{data: mdcPlaintext, stride: stride}
+ mdcReader := &seMDCReader{in: r, h: sha1.New()}
+ body, err := ioutil.ReadAll(mdcReader)
+ if err != nil {
+ t.Errorf("stride: %d, error: %s", stride, err)
+ continue
+ }
+ if !bytes.Equal(body, mdcPlaintext[:len(mdcPlaintext)-22]) {
+ t.Errorf("stride: %d: bad contents %x", stride, body)
+ continue
+ }
+
+ err = mdcReader.Close()
+ if err != nil {
+ t.Errorf("stride: %d, error on Close: %s", stride, err)
+ }
+ }
+
+ mdcPlaintext[15] ^= 80
+
+ r := &testReader{data: mdcPlaintext, stride: 2}
+ mdcReader := &seMDCReader{in: r, h: sha1.New()}
+ _, err := ioutil.ReadAll(mdcReader)
+ if err != nil {
+ t.Errorf("corruption test, error: %s", err)
+ return
+ }
+ err = mdcReader.Close()
+ if err == nil {
+ t.Error("corruption: no error")
+ } else if _, ok := err.(*errors.SignatureError); !ok {
+ t.Errorf("corruption: expected SignatureError, got: %s", err)
+ }
+}
+
+const mdcPlaintextHex = "a302789c3b2d93c4e0eb9aba22283539b3203335af44a134afb800c849cb4c4de10200aff40b45d31432c80cb384299a0655966d6939dfdeed1dddf980"
+
+func TestSerialize(t *testing.T) {
+ buf := bytes.NewBuffer(nil)
+ c := CipherAES128
+ key := make([]byte, c.KeySize())
+
+ w, err := SerializeSymmetricallyEncrypted(buf, rand.Reader, c, key)
+ if err != nil {
+ t.Errorf("error from SerializeSymmetricallyEncrypted: %s", err)
+ return
+ }
+
+ contents := []byte("hello world\n")
+
+ w.Write(contents)
+ w.Close()
+
+ p, err := Read(buf)
+ if err != nil {
+ t.Errorf("error from Read: %s", err)
+ return
+ }
+
+ se, ok := p.(*SymmetricallyEncrypted)
+ if !ok {
+ t.Errorf("didn't read a *SymmetricallyEncrypted")
+ return
+ }
+
+ r, err := se.Decrypt(c, key)
+ if err != nil {
+ t.Errorf("error from Decrypt: %s", err)
+ return
+ }
+
+ contentsCopy := bytes.NewBuffer(nil)
+ _, err = io.Copy(contentsCopy, r)
+ if err != nil {
+ t.Errorf("error from io.Copy: %s", err)
+ return
+ }
+ if !bytes.Equal(contentsCopy.Bytes(), contents) {
+ t.Errorf("contents not equal got: %x want: %x", contentsCopy.Bytes(), contents)
+ }
+}
diff --git a/openpgp/packet/userid.go b/openpgp/packet/userid.go
new file mode 100644
index 0000000..d6bea7d
--- /dev/null
+++ b/openpgp/packet/userid.go
@@ -0,0 +1,160 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "io"
+ "io/ioutil"
+ "strings"
+)
+
+// UserId contains text that is intended to represent the name and email
+// address of the key holder. See RFC 4880, section 5.11. By convention, this
+// takes the form "Full Name (Comment) <email@example.com>"
+type UserId struct {
+ Id string // By convention, this takes the form "Full Name (Comment) <email@example.com>" which is split out in the fields below.
+
+ Name, Comment, Email string
+}
+
+func hasInvalidCharacters(s string) bool {
+ for _, c := range s {
+ switch c {
+ case '(', ')', '<', '>', 0:
+ return true
+ }
+ }
+ return false
+}
+
+// NewUserId returns a UserId or nil if any of the arguments contain invalid
+// characters. The invalid characters are '\x00', '(', ')', '<' and '>'
+func NewUserId(name, comment, email string) *UserId {
+ // RFC 4880 doesn't deal with the structure of userid strings; the
+ // name, comment and email form is just a convention. However, there's
+ // no convention about escaping the metacharacters and GPG just refuses
+ // to create user ids where, say, the name contains a '('. We mirror
+ // this behaviour.
+
+ if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) {
+ return nil
+ }
+
+ uid := new(UserId)
+ uid.Name, uid.Comment, uid.Email = name, comment, email
+ uid.Id = name
+ if len(comment) > 0 {
+ if len(uid.Id) > 0 {
+ uid.Id += " "
+ }
+ uid.Id += "("
+ uid.Id += comment
+ uid.Id += ")"
+ }
+ if len(email) > 0 {
+ if len(uid.Id) > 0 {
+ uid.Id += " "
+ }
+ uid.Id += "<"
+ uid.Id += email
+ uid.Id += ">"
+ }
+ return uid
+}
+
+func (uid *UserId) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.11
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ return
+ }
+ uid.Id = string(b)
+ uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id)
+ return
+}
+
+// Serialize marshals uid to w in the form of an OpenPGP packet, including
+// header.
+func (uid *UserId) Serialize(w io.Writer) error {
+ err := serializeHeader(w, packetTypeUserId, len(uid.Id))
+ if err != nil {
+ return err
+ }
+ _, err = w.Write([]byte(uid.Id))
+ return err
+}
+
+// parseUserId extracts the name, comment and email from a user id string that
+// is formatted as "Full Name (Comment) <email@example.com>".
+func parseUserId(id string) (name, comment, email string) {
+ var n, c, e struct {
+ start, end int
+ }
+ var state int
+
+ for offset, rune := range id {
+ switch state {
+ case 0:
+ // Entering name
+ n.start = offset
+ state = 1
+ fallthrough
+ case 1:
+ // In name
+ if rune == '(' {
+ state = 2
+ n.end = offset
+ } else if rune == '<' {
+ state = 5
+ n.end = offset
+ }
+ case 2:
+ // Entering comment
+ c.start = offset
+ state = 3
+ fallthrough
+ case 3:
+ // In comment
+ if rune == ')' {
+ state = 4
+ c.end = offset
+ }
+ case 4:
+ // Between comment and email
+ if rune == '<' {
+ state = 5
+ }
+ case 5:
+ // Entering email
+ e.start = offset
+ state = 6
+ fallthrough
+ case 6:
+ // In email
+ if rune == '>' {
+ state = 7
+ e.end = offset
+ }
+ default:
+ // After email
+ }
+ }
+ switch state {
+ case 1:
+ // ended in the name
+ n.end = len(id)
+ case 3:
+ // ended in comment
+ c.end = len(id)
+ case 6:
+ // ended in email
+ e.end = len(id)
+ }
+
+ name = strings.TrimSpace(id[n.start:n.end])
+ comment = strings.TrimSpace(id[c.start:c.end])
+ email = strings.TrimSpace(id[e.start:e.end])
+ return
+}
diff --git a/openpgp/packet/userid_test.go b/openpgp/packet/userid_test.go
new file mode 100644
index 0000000..2968193
--- /dev/null
+++ b/openpgp/packet/userid_test.go
@@ -0,0 +1,87 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+ "testing"
+)
+
+var userIdTests = []struct {
+ id string
+ name, comment, email string
+}{
+ {"", "", "", ""},
+ {"John Smith", "John Smith", "", ""},
+ {"John Smith ()", "John Smith", "", ""},
+ {"John Smith () <>", "John Smith", "", ""},
+ {"(comment", "", "comment", ""},
+ {"(comment)", "", "comment", ""},
+ {"<email", "", "", "email"},
+ {"<email> sdfk", "", "", "email"},
+ {" John Smith ( Comment ) asdkflj < email > lksdfj", "John Smith", "Comment", "email"},
+ {" John Smith < email > lksdfj", "John Smith", "", "email"},
+ {"(<foo", "", "<foo", ""},
+ {"René Descartes (العربي)", "René Descartes", "العربي", ""},
+}
+
+func TestParseUserId(t *testing.T) {
+ for i, test := range userIdTests {
+ name, comment, email := parseUserId(test.id)
+ if name != test.name {
+ t.Errorf("%d: name mismatch got:%s want:%s", i, name, test.name)
+ }
+ if comment != test.comment {
+ t.Errorf("%d: comment mismatch got:%s want:%s", i, comment, test.comment)
+ }
+ if email != test.email {
+ t.Errorf("%d: email mismatch got:%s want:%s", i, email, test.email)
+ }
+ }
+}
+
+var newUserIdTests = []struct {
+ name, comment, email, id string
+}{
+ {"foo", "", "", "foo"},
+ {"", "bar", "", "(bar)"},
+ {"", "", "baz", "<baz>"},
+ {"foo", "bar", "", "foo (bar)"},
+ {"foo", "", "baz", "foo <baz>"},
+ {"", "bar", "baz", "(bar) <baz>"},
+ {"foo", "bar", "baz", "foo (bar) <baz>"},
+}
+
+func TestNewUserId(t *testing.T) {
+ for i, test := range newUserIdTests {
+ uid := NewUserId(test.name, test.comment, test.email)
+ if uid == nil {
+ t.Errorf("#%d: returned nil", i)
+ continue
+ }
+ if uid.Id != test.id {
+ t.Errorf("#%d: got '%s', want '%s'", i, uid.Id, test.id)
+ }
+ }
+}
+
+var invalidNewUserIdTests = []struct {
+ name, comment, email string
+}{
+ {"foo(", "", ""},
+ {"foo<", "", ""},
+ {"", "bar)", ""},
+ {"", "bar<", ""},
+ {"", "", "baz>"},
+ {"", "", "baz)"},
+ {"", "", "baz\x00"},
+}
+
+func TestNewUserIdWithInvalidInput(t *testing.T) {
+ for i, test := range invalidNewUserIdTests {
+ if uid := NewUserId(test.name, test.comment, test.email); uid != nil {
+ t.Errorf("#%d: returned non-nil value: %#v", i, uid)
+ }
+ }
+}
diff --git a/openpgp/read.go b/openpgp/read.go
new file mode 100644
index 0000000..1eec1b1
--- /dev/null
+++ b/openpgp/read.go
@@ -0,0 +1,414 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package openpgp implements high level operations on OpenPGP messages.
+package openpgp
+
+import (
+ "code.google.com/p/go.crypto/openpgp/armor"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "code.google.com/p/go.crypto/openpgp/packet"
+ "crypto"
+ _ "crypto/sha256"
+ "hash"
+ "io"
+ "strconv"
+)
+
+// SignatureType is the armor type for a PGP signature.
+var SignatureType = "PGP SIGNATURE"
+
+// readArmored reads an armored block with the given type.
+func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) {
+ block, err := armor.Decode(r)
+ if err != nil {
+ return
+ }
+
+ if block.Type != expectedType {
+ return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type)
+ }
+
+ return block.Body, nil
+}
+
+// MessageDetails contains the result of parsing an OpenPGP encrypted and/or
+// signed message.
+type MessageDetails struct {
+ IsEncrypted bool // true if the message was encrypted.
+ EncryptedToKeyIds []uint64 // the list of recipient key ids.
+ IsSymmetricallyEncrypted bool // true if a passphrase could have decrypted the message.
+ DecryptedWith Key // the private key used to decrypt the message, if any.
+ IsSigned bool // true if the message is signed.
+ SignedByKeyId uint64 // the key id of the signer, if any.
+ SignedBy *Key // the key of the signer, if available.
+ LiteralData *packet.LiteralData // the metadata of the contents
+ UnverifiedBody io.Reader // the contents of the message.
+
+ // If IsSigned is true and SignedBy is non-zero then the signature will
+ // be verified as UnverifiedBody is read. The signature cannot be
+ // checked until the whole of UnverifiedBody is read so UnverifiedBody
+ // must be consumed until EOF before the data can trusted. Even if a
+ // message isn't signed (or the signer is unknown) the data may contain
+ // an authentication code that is only checked once UnverifiedBody has
+ // been consumed. Once EOF has been seen, the following fields are
+ // valid. (An authentication code failure is reported as a
+ // SignatureError error when reading from UnverifiedBody.)
+ SignatureError error // nil if the signature is good.
+ Signature *packet.Signature // the signature packet itself.
+
+ decrypted io.ReadCloser
+}
+
+// A PromptFunction is used as a callback by functions that may need to decrypt
+// a private key, or prompt for a passphrase. It is called with a list of
+// acceptable, encrypted private keys and a boolean that indicates whether a
+// passphrase is usable. It should either decrypt a private key or return a
+// passphrase to try. If the decrypted private key or given passphrase isn't
+// correct, the function will be called again, forever. Any error returned will
+// be passed up.
+type PromptFunction func(keys []Key, symmetric bool) ([]byte, error)
+
+// A keyEnvelopePair is used to store a private key with the envelope that
+// contains a symmetric key, encrypted with that key.
+type keyEnvelopePair struct {
+ key Key
+ encryptedKey *packet.EncryptedKey
+}
+
+// ReadMessage parses an OpenPGP message that may be signed and/or encrypted.
+// The given KeyRing should contain both public keys (for signature
+// verification) and, possibly encrypted, private keys for decrypting.
+func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction) (md *MessageDetails, err error) {
+ var p packet.Packet
+
+ var symKeys []*packet.SymmetricKeyEncrypted
+ var pubKeys []keyEnvelopePair
+ var se *packet.SymmetricallyEncrypted
+
+ packets := packet.NewReader(r)
+ md = new(MessageDetails)
+ md.IsEncrypted = true
+
+ // The message, if encrypted, starts with a number of packets
+ // containing an encrypted decryption key. The decryption key is either
+ // encrypted to a public key, or with a passphrase. This loop
+ // collects these packets.
+ParsePackets:
+ for {
+ p, err = packets.Next()
+ if err != nil {
+ return nil, err
+ }
+ switch p := p.(type) {
+ case *packet.SymmetricKeyEncrypted:
+ // This packet contains the decryption key encrypted with a passphrase.
+ md.IsSymmetricallyEncrypted = true
+ symKeys = append(symKeys, p)
+ case *packet.EncryptedKey:
+ // This packet contains the decryption key encrypted to a public key.
+ md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId)
+ switch p.Algo {
+ case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal:
+ break
+ default:
+ continue
+ }
+ var keys []Key
+ if p.KeyId == 0 {
+ keys = keyring.DecryptionKeys()
+ } else {
+ keys = keyring.KeysById(p.KeyId)
+ }
+ for _, k := range keys {
+ pubKeys = append(pubKeys, keyEnvelopePair{k, p})
+ }
+ case *packet.SymmetricallyEncrypted:
+ se = p
+ break ParsePackets
+ case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature:
+ // This message isn't encrypted.
+ if len(symKeys) != 0 || len(pubKeys) != 0 {
+ return nil, errors.StructuralError("key material not followed by encrypted message")
+ }
+ packets.Unread(p)
+ return readSignedMessage(packets, nil, keyring)
+ }
+ }
+
+ var candidates []Key
+ var decrypted io.ReadCloser
+
+ // Now that we have the list of encrypted keys we need to decrypt at
+ // least one of them or, if we cannot, we need to call the prompt
+ // function so that it can decrypt a key or give us a passphrase.
+FindKey:
+ for {
+ // See if any of the keys already have a private key available
+ candidates = candidates[:0]
+ candidateFingerprints := make(map[string]bool)
+
+ for _, pk := range pubKeys {
+ if pk.key.PrivateKey == nil {
+ continue
+ }
+ if !pk.key.PrivateKey.Encrypted {
+ if len(pk.encryptedKey.Key) == 0 {
+ pk.encryptedKey.Decrypt(pk.key.PrivateKey)
+ }
+ if len(pk.encryptedKey.Key) == 0 {
+ continue
+ }
+ decrypted, err = se.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key)
+ if err != nil && err != errors.ErrKeyIncorrect {
+ return nil, err
+ }
+ if decrypted != nil {
+ md.DecryptedWith = pk.key
+ break FindKey
+ }
+ } else {
+ fpr := string(pk.key.PublicKey.Fingerprint[:])
+ if v := candidateFingerprints[fpr]; v {
+ continue
+ }
+ candidates = append(candidates, pk.key)
+ candidateFingerprints[fpr] = true
+ }
+ }
+
+ if len(candidates) == 0 && len(symKeys) == 0 {
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ if prompt == nil {
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ passphrase, err := prompt(candidates, len(symKeys) != 0)
+ if err != nil {
+ return nil, err
+ }
+
+ // Try the symmetric passphrase first
+ if len(symKeys) != 0 && passphrase != nil {
+ for _, s := range symKeys {
+ err = s.Decrypt(passphrase)
+ if err == nil && !s.Encrypted {
+ decrypted, err = se.Decrypt(s.CipherFunc, s.Key)
+ if err != nil && err != errors.ErrKeyIncorrect {
+ return nil, err
+ }
+ if decrypted != nil {
+ break FindKey
+ }
+ }
+
+ }
+ }
+ }
+
+ md.decrypted = decrypted
+ packets.Push(decrypted)
+ return readSignedMessage(packets, md, keyring)
+}
+
+// readSignedMessage reads a possibly signed message if mdin is non-zero then
+// that structure is updated and returned. Otherwise a fresh MessageDetails is
+// used.
+func readSignedMessage(packets *packet.Reader, mdin *MessageDetails, keyring KeyRing) (md *MessageDetails, err error) {
+ if mdin == nil {
+ mdin = new(MessageDetails)
+ }
+ md = mdin
+
+ var p packet.Packet
+ var h hash.Hash
+ var wrappedHash hash.Hash
+FindLiteralData:
+ for {
+ p, err = packets.Next()
+ if err != nil {
+ return nil, err
+ }
+ switch p := p.(type) {
+ case *packet.Compressed:
+ packets.Push(p.Body)
+ case *packet.OnePassSignature:
+ if !p.IsLast {
+ return nil, errors.UnsupportedError("nested signatures")
+ }
+
+ h, wrappedHash, err = hashForSignature(p.Hash, p.SigType)
+ if err != nil {
+ md = nil
+ return
+ }
+
+ md.IsSigned = true
+ md.SignedByKeyId = p.KeyId
+ keys := keyring.KeysById(p.KeyId)
+ for i, key := range keys {
+ if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign {
+ continue
+ }
+ md.SignedBy = &keys[i]
+ break
+ }
+ case *packet.LiteralData:
+ md.LiteralData = p
+ break FindLiteralData
+ }
+ }
+
+ if md.SignedBy != nil {
+ md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md}
+ } else if md.decrypted != nil {
+ md.UnverifiedBody = checkReader{md}
+ } else {
+ md.UnverifiedBody = md.LiteralData.Body
+ }
+
+ return md, nil
+}
+
+// hashForSignature returns a pair of hashes that can be used to verify a
+// signature. The signature may specify that the contents of the signed message
+// should be preprocessed (i.e. to normalize line endings). Thus this function
+// returns two hashes. The second should be used to hash the message itself and
+// performs any needed preprocessing.
+func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
+ h := hashId.New()
+ if h == nil {
+ return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId)))
+ }
+
+ switch sigType {
+ case packet.SigTypeBinary:
+ return h, h, nil
+ case packet.SigTypeText:
+ return h, NewCanonicalTextHash(h), nil
+ }
+
+ return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType)))
+}
+
+// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF
+// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger
+// MDC checks.
+type checkReader struct {
+ md *MessageDetails
+}
+
+func (cr checkReader) Read(buf []byte) (n int, err error) {
+ n, err = cr.md.LiteralData.Body.Read(buf)
+ if err == io.EOF {
+ mdcErr := cr.md.decrypted.Close()
+ if mdcErr != nil {
+ err = mdcErr
+ }
+ }
+ return
+}
+
+// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes
+// the data as it is read. When it sees an EOF from the underlying io.Reader
+// it parses and checks a trailing Signature packet and triggers any MDC checks.
+type signatureCheckReader struct {
+ packets *packet.Reader
+ h, wrappedHash hash.Hash
+ md *MessageDetails
+}
+
+func (scr *signatureCheckReader) Read(buf []byte) (n int, err error) {
+ n, err = scr.md.LiteralData.Body.Read(buf)
+ scr.wrappedHash.Write(buf[:n])
+ if err == io.EOF {
+ var p packet.Packet
+ p, scr.md.SignatureError = scr.packets.Next()
+ if scr.md.SignatureError != nil {
+ return
+ }
+
+ var ok bool
+ if scr.md.Signature, ok = p.(*packet.Signature); !ok {
+ scr.md.SignatureError = errors.StructuralError("LiteralData not followed by Signature")
+ return
+ }
+
+ scr.md.SignatureError = scr.md.SignedBy.PublicKey.VerifySignature(scr.h, scr.md.Signature)
+
+ // The SymmetricallyEncrypted packet, if any, might have an
+ // unsigned hash of its own. In order to check this we need to
+ // close that Reader.
+ if scr.md.decrypted != nil {
+ mdcErr := scr.md.decrypted.Close()
+ if mdcErr != nil {
+ err = mdcErr
+ }
+ }
+ }
+ return
+}
+
+// CheckDetachedSignature takes a signed file and a detached signature and
+// returns the signer if the signature is valid. If the signer isn't known,
+// ErrUnknownIssuer is returned.
+func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) {
+ p, err := packet.Read(signature)
+ if err != nil {
+ return
+ }
+
+ sig, ok := p.(*packet.Signature)
+ if !ok {
+ return nil, errors.StructuralError("non signature packet found")
+ }
+
+ if sig.IssuerKeyId == nil {
+ return nil, errors.StructuralError("signature doesn't have an issuer")
+ }
+
+ keys := keyring.KeysById(*sig.IssuerKeyId)
+ if len(keys) == 0 {
+ return nil, errors.ErrUnknownIssuer
+ }
+
+ h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
+ if err != nil {
+ return
+ }
+
+ _, err = io.Copy(wrappedHash, signed)
+ if err != nil && err != io.EOF {
+ return
+ }
+
+ for _, key := range keys {
+ if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign {
+ continue
+ }
+ err = key.PublicKey.VerifySignature(h, sig)
+ if err == nil {
+ return key.Entity, nil
+ }
+ }
+
+ if err != nil {
+ return
+ }
+
+ return nil, errors.ErrUnknownIssuer
+}
+
+// CheckArmoredDetachedSignature performs the same actions as
+// CheckDetachedSignature but expects the signature to be armored.
+func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) {
+ body, err := readArmored(signature, SignatureType)
+ if err != nil {
+ return
+ }
+
+ return CheckDetachedSignature(keyring, signed, body)
+}
diff --git a/openpgp/read_test.go b/openpgp/read_test.go
new file mode 100644
index 0000000..18fcfe6
--- /dev/null
+++ b/openpgp/read_test.go
@@ -0,0 +1,372 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+ "bytes"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ _ "crypto/sha512"
+ "encoding/hex"
+ "io"
+ "io/ioutil"
+ "testing"
+)
+
+func readerFromHex(s string) io.Reader {
+ data, err := hex.DecodeString(s)
+ if err != nil {
+ panic("readerFromHex: bad input")
+ }
+ return bytes.NewBuffer(data)
+}
+
+func TestReadKeyRing(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B {
+ t.Errorf("bad keyring: %#v", kring)
+ }
+}
+
+func TestRereadKeyRing(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ if err != nil {
+ t.Errorf("error in initial parse: %s", err)
+ return
+ }
+ out := new(bytes.Buffer)
+ err = kring[0].Serialize(out)
+ if err != nil {
+ t.Errorf("error in serialization: %s", err)
+ return
+ }
+ kring, err = ReadKeyRing(out)
+ if err != nil {
+ t.Errorf("error in second parse: %s", err)
+ return
+ }
+
+ if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB {
+ t.Errorf("bad keyring: %#v", kring)
+ }
+}
+
+func TestReadPrivateKeyRing(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B || kring[0].PrimaryKey == nil {
+ t.Errorf("bad keyring: %#v", kring)
+ }
+}
+
+func TestReadDSAKey(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(dsaTestKeyHex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0x0CCC0360 {
+ t.Errorf("bad parse: %#v", kring)
+ }
+}
+
+func TestDSAHashTruncatation(t *testing.T) {
+ // dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo
+ // SHA512 in order to require DSA hash truncation to verify correctly.
+ _, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512))
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestGetKeyById(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+ keys := kring.KeysById(0xa34d7e18c20c31bb)
+ if len(keys) != 1 || keys[0].Entity != kring[0] {
+ t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+ }
+
+ keys = kring.KeysById(0xfd94408d4543314f)
+ if len(keys) != 1 || keys[0].Entity != kring[0] {
+ t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+ }
+}
+
+func checkSignedMessage(t *testing.T, signedHex, expected string) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+ md, err := ReadMessage(readerFromHex(signedHex), kring, nil)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ if !md.IsSigned || md.SignedByKeyId != 0xa34d7e18c20c31bb || md.SignedBy == nil || md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) != 0 || md.IsSymmetricallyEncrypted {
+ t.Errorf("bad MessageDetails: %#v", md)
+ }
+
+ contents, err := ioutil.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("error reading UnverifiedBody: %s", err)
+ }
+ if string(contents) != expected {
+ t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+ }
+ if md.SignatureError != nil || md.Signature == nil {
+ t.Errorf("failed to validate: %s", md.SignatureError)
+ }
+}
+
+func TestSignedMessage(t *testing.T) {
+ checkSignedMessage(t, signedMessageHex, signedInput)
+}
+
+func TestTextSignedMessage(t *testing.T) {
+ checkSignedMessage(t, signedTextMessageHex, signedTextInput)
+}
+
+var signedEncryptedMessageTests = []struct {
+ keyRingHex string
+ messageHex string
+ signedByKeyId uint64
+ encryptedToKeyId uint64
+}{
+ {
+ testKeys1And2PrivateHex,
+ signedEncryptedMessageHex,
+ 0xa34d7e18c20c31bb,
+ 0x2a67d68660df41c7,
+ },
+ {
+ dsaElGamalTestKeysHex,
+ signedEncryptedMessage2Hex,
+ 0x33af447ccd759b09,
+ 0xcf6a7abcd43e3673,
+ },
+}
+
+func TestSignedEncryptedMessage(t *testing.T) {
+ for i, test := range signedEncryptedMessageTests {
+ expected := "Signed and encrypted message\n"
+ kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
+ prompt := func(keys []Key, symmetric bool) ([]byte, error) {
+ if symmetric {
+ t.Errorf("prompt: message was marked as symmetrically encrypted")
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ if len(keys) == 0 {
+ t.Error("prompt: no keys requested")
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ err := keys[0].PrivateKey.Decrypt([]byte("passphrase"))
+ if err != nil {
+ t.Errorf("prompt: error decrypting key: %s", err)
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ return nil, nil
+ }
+
+ md, err := ReadMessage(readerFromHex(test.messageHex), kring, prompt)
+ if err != nil {
+ t.Errorf("#%d: error reading message: %s", i, err)
+ return
+ }
+
+ if !md.IsSigned || md.SignedByKeyId != test.signedByKeyId || md.SignedBy == nil || !md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) == 0 || md.EncryptedToKeyIds[0] != test.encryptedToKeyId {
+ t.Errorf("#%d: bad MessageDetails: %#v", i, md)
+ }
+
+ contents, err := ioutil.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("#%d: error reading UnverifiedBody: %s", i, err)
+ }
+ if string(contents) != expected {
+ t.Errorf("#%d: bad UnverifiedBody got:%s want:%s", i, string(contents), expected)
+ }
+
+ if md.SignatureError != nil || md.Signature == nil {
+ t.Errorf("#%d: failed to validate: %s", i, md.SignatureError)
+ }
+ }
+}
+
+func TestUnspecifiedRecipient(t *testing.T) {
+ expected := "Recipient unspecified\n"
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+
+ md, err := ReadMessage(readerFromHex(recipientUnspecifiedHex), kring, nil)
+ if err != nil {
+ t.Errorf("error reading message: %s", err)
+ return
+ }
+
+ contents, err := ioutil.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("error reading UnverifiedBody: %s", err)
+ }
+ if string(contents) != expected {
+ t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+ }
+}
+
+func TestSymmetricallyEncrypted(t *testing.T) {
+ expected := "Symmetrically encrypted.\n"
+
+ prompt := func(keys []Key, symmetric bool) ([]byte, error) {
+ if len(keys) != 0 {
+ t.Errorf("prompt: len(keys) = %d (want 0)", len(keys))
+ }
+
+ if !symmetric {
+ t.Errorf("symmetric is not set")
+ }
+
+ return []byte("password"), nil
+ }
+
+ md, err := ReadMessage(readerFromHex(symmetricallyEncryptedCompressedHex), nil, prompt)
+ if err != nil {
+ t.Errorf("ReadMessage: %s", err)
+ return
+ }
+
+ contents, err := ioutil.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("ReadAll: %s", err)
+ }
+
+ expectedCreationTime := uint32(1295992998)
+ if md.LiteralData.Time != expectedCreationTime {
+ t.Errorf("LiteralData.Time is %d, want %d", md.LiteralData.Time, expectedCreationTime)
+ }
+
+ if string(contents) != expected {
+ t.Errorf("contents got: %s want: %s", string(contents), expected)
+ }
+}
+
+func testDetachedSignature(t *testing.T, kring KeyRing, signature io.Reader, sigInput, tag string, expectedSignerKeyId uint64) {
+ signed := bytes.NewBufferString(sigInput)
+ signer, err := CheckDetachedSignature(kring, signed, signature)
+ if err != nil {
+ t.Errorf("%s: signature error: %s", tag, err)
+ return
+ }
+ if signer == nil {
+ t.Errorf("%s: signer is nil", tag)
+ return
+ }
+ if signer.PrimaryKey.KeyId != expectedSignerKeyId {
+ t.Errorf("%s: wrong signer got:%x want:%x", tag, signer.PrimaryKey.KeyId, expectedSignerKeyId)
+ }
+}
+
+func TestDetachedSignature(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureHex), signedInput, "binary", testKey1KeyId)
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureTextHex), signedInput, "text", testKey1KeyId)
+}
+
+func TestDetachedSignatureDSA(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyHex))
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId)
+}
+
+func TestReadingArmoredPrivateKey(t *testing.T) {
+ el, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock))
+ if err != nil {
+ t.Error(err)
+ }
+ if len(el) != 1 {
+ t.Errorf("got %d entities, wanted 1\n", len(el))
+ }
+}
+
+func TestNoArmoredData(t *testing.T) {
+ _, err := ReadArmoredKeyRing(bytes.NewBufferString("foo"))
+ if _, ok := err.(errors.InvalidArgumentError); !ok {
+ t.Errorf("error was not an InvalidArgumentError: %s", err)
+ }
+}
+
+const testKey1KeyId = 0xA34D7E18C20C31BB
+const testKey3KeyId = 0x338934250CCC0360
+
+const signedInput = "Signed message\nline 2\nline 3\n"
+const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n"
+
+const recipientUnspecifiedHex = "848c0300000000000000000103ff62d4d578d03cf40c3da998dfe216c074fa6ddec5e31c197c9666ba292830d91d18716a80f699f9d897389a90e6d62d0238f5f07a5248073c0f24920e4bc4a30c2d17ee4e0cae7c3d4aaa4e8dced50e3010a80ee692175fa0385f62ecca4b56ee6e9980aa3ec51b61b077096ac9e800edaf161268593eedb6cc7027ff5cb32745d250010d407a6221ae22ef18469b444f2822478c4d190b24d36371a95cb40087cdd42d9399c3d06a53c0673349bfb607927f20d1e122bde1e2bf3aa6cae6edf489629bcaa0689539ae3b718914d88ededc3b"
+
+const detachedSignatureHex = "889c04000102000605024d449cd1000a0910a34d7e18c20c31bb167603ff57718d09f28a519fdc7b5a68b6a3336da04df85e38c5cd5d5bd2092fa4629848a33d85b1729402a2aab39c3ac19f9d573f773cc62c264dc924c067a79dfd8a863ae06c7c8686120760749f5fd9b1e03a64d20a7df3446ddc8f0aeadeaeba7cbaee5c1e366d65b6a0c6cc749bcb912d2f15013f812795c2e29eb7f7b77f39ce77"
+
+const detachedSignatureTextHex = "889c04010102000605024d449d21000a0910a34d7e18c20c31bbc8c60400a24fbef7342603a41cb1165767bd18985d015fb72fe05db42db36cfb2f1d455967f1e491194fbf6cf88146222b23bf6ffbd50d17598d976a0417d3192ff9cc0034fd00f287b02e90418bbefe609484b09231e4e7a5f3562e199bf39909ab5276c4d37382fe088f6b5c3426fc1052865da8b3ab158672d58b6264b10823dc4b39"
+
+const detachedSignatureDSAHex = "884604001102000605024d6c4eac000a0910338934250ccc0360f18d00a087d743d6405ed7b87755476629600b8b694a39e900a0abff8126f46faf1547c1743c37b21b4ea15b8f83"
+
+const testKeys1And2Hex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b0020003b88d044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f0011010001889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab0020003988d044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b0020003b88d044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020003"
+
+const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd00110100010003ff4d91393b9a8e3430b14d6209df42f98dc927425b881f1209f319220841273a802a97c7bdb8b3a7740b3ab5866c4d1d308ad0d3a79bd1e883aacf1ac92dfe720285d10d08752a7efe3c609b1d00f17f2805b217be53999a7da7e493bfc3e9618fd17018991b8128aea70a05dbce30e4fbe626aa45775fa255dd9177aabf4df7cf0200c1ded12566e4bc2bb590455e5becfb2e2c9796482270a943343a7835de41080582c2be3caf5981aa838140e97afa40ad652a0b544f83eb1833b0957dce26e47b0200eacd6046741e9ce2ec5beb6fb5e6335457844fb09477f83b050a96be7da043e17f3a9523567ed40e7a521f818813a8b8a72209f1442844843ccc7eb9805442570200bdafe0438d97ac36e773c7162028d65844c4d463e2420aa2228c6e50dc2743c3d6c72d0d782a5173fe7be2169c8a9f4ef8a7cf3e37165e8c61b89c346cdc6c1799d2b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b00200009d01d8044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f00110100010003fd17a7490c22a79c59281fb7b20f5e6553ec0c1637ae382e8adaea295f50241037f8997cf42c1ce26417e015091451b15424b2c59eb8d4161b0975630408e394d3b00f88d4b4e18e2cc85e8251d4753a27c639c83f5ad4a571c4f19d7cd460b9b73c25ade730c99df09637bd173d8e3e981ac64432078263bb6dc30d3e974150dd0200d0ee05be3d4604d2146fb0457f31ba17c057560785aa804e8ca5530a7cd81d3440d0f4ba6851efcfd3954b7e68908fc0ba47f7ac37bf559c6c168b70d3a7c8cd0200da1c677c4bce06a068070f2b3733b0a714e88d62aa3f9a26c6f5216d48d5c2b5624144f3807c0df30be66b3268eeeca4df1fbded58faf49fc95dc3c35f134f8b01fd1396b6c0fc1b6c4f0eb8f5e44b8eace1e6073e20d0b8bc5385f86f1cf3f050f66af789f3ef1fc107b7f4421e19e0349c730c68f0a226981f4e889054fdb4dc149e8e889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab00200009501fe044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001fe030302e9030f3c783e14856063f16938530e148bc57a7aa3f3e4f90df9dceccdc779bc0835e1ad3d006e4a8d7b36d08b8e0de5a0d947254ecfbd22037e6572b426bcfdc517796b224b0036ff90bc574b5509bede85512f2eefb520fb4b02aa523ba739bff424a6fe81c5041f253f8d757e69a503d3563a104d0d49e9e890b9d0c26f96b55b743883b472caa7050c4acfd4a21f875bdf1258d88bd61224d303dc9df77f743137d51e6d5246b88c406780528fd9a3e15bab5452e5b93970d9dcc79f48b38651b9f15bfbcf6da452837e9cc70683d1bdca94507870f743e4ad902005812488dd342f836e72869afd00ce1850eea4cfa53ce10e3608e13d3c149394ee3cbd0e23d018fcbcb6e2ec5a1a22972d1d462ca05355d0d290dd2751e550d5efb38c6c89686344df64852bf4ff86638708f644e8ec6bd4af9b50d8541cb91891a431326ab2e332faa7ae86cfb6e0540aa63160c1e5cdd5a4add518b303fff0a20117c6bc77f7cfbaf36b04c865c6c2b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b00200009d01fe044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001fe030302e9030f3c783e148560f936097339ae381d63116efcf802ff8b1c9360767db5219cc987375702a4123fd8657d3e22700f23f95020d1b261eda5257e9a72f9a918e8ef22dd5b3323ae03bbc1923dd224db988cadc16acc04b120a9f8b7e84da9716c53e0334d7b66586ddb9014df604b41be1e960dcfcbc96f4ed150a1a0dd070b9eb14276b9b6be413a769a75b519a53d3ecc0c220e85cd91ca354d57e7344517e64b43b6e29823cbd87eae26e2b2e78e6dedfbb76e3e9f77bcb844f9a8932eb3db2c3f9e44316e6f5d60e9e2a56e46b72abe6b06dc9a31cc63f10023d1f5e12d2a3ee93b675c96f504af0001220991c88db759e231b3320dcedf814dcf723fd9857e3d72d66a0f2af26950b915abdf56c1596f46a325bf17ad4810d3535fb02a259b247ac3dbd4cc3ecf9c51b6c07cebb009c1506fba0a89321ec8683e3fd009a6e551d50243e2d5092fefb3321083a4bad91320dc624bd6b5dddf93553e3d53924c05bfebec1fb4bd47e89a1a889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020000"
+
+const dsaElGamalTestKeysHex = "9501e1044dfcb16a110400aa3e5c1a1f43dd28c2ffae8abf5cfce555ee874134d8ba0a0f7b868ce2214beddc74e5e1e21ded354a95d18acdaf69e5e342371a71fbb9093162e0c5f3427de413a7f2c157d83f5cd2f9d791256dc4f6f0e13f13c3302af27f2384075ab3021dff7a050e14854bbde0a1094174855fc02f0bae8e00a340d94a1f22b32e48485700a0cec672ac21258fb95f61de2ce1af74b2c4fa3e6703ff698edc9be22c02ae4d916e4fa223f819d46582c0516235848a77b577ea49018dcd5e9e15cff9dbb4663a1ae6dd7580fa40946d40c05f72814b0f88481207e6c0832c3bded4853ebba0a7e3bd8e8c66df33d5a537cd4acf946d1080e7a3dcea679cb2b11a72a33a2b6a9dc85f466ad2ddf4c3db6283fa645343286971e3dd700703fc0c4e290d45767f370831a90187e74e9972aae5bff488eeff7d620af0362bfb95c1a6c3413ab5d15a2e4139e5d07a54d72583914661ed6a87cce810be28a0aa8879a2dd39e52fb6fe800f4f181ac7e328f740cde3d09a05cecf9483e4cca4253e60d4429ffd679d9996a520012aad119878c941e3cf151459873bdfc2a9563472fe0303027a728f9feb3b864260a1babe83925ce794710cfd642ee4ae0e5b9d74cee49e9c67b6cd0ea5dfbb582132195a121356a1513e1bca73e5b80c58c7ccb4164453412f456c47616d616c2054657374204b65792031886204131102002205024dfcb16a021b03060b090807030206150802090a0b0416020301021e01021780000a091033af447ccd759b09fadd00a0b8fd6f5a790bad7e9f2dbb7632046dc4493588db009c087c6a9ba9f7f49fab221587a74788c00db4889ab00200009d0157044dfcb16a1004008dec3f9291205255ccff8c532318133a6840739dd68b03ba942676f9038612071447bf07d00d559c5c0875724ea16a4c774f80d8338b55fca691a0522e530e604215b467bbc9ccfd483a1da99d7bc2648b4318fdbd27766fc8bfad3fddb37c62b8ae7ccfe9577e9b8d1e77c1d417ed2c2ef02d52f4da11600d85d3229607943700030503ff506c94c87c8cab778e963b76cf63770f0a79bf48fb49d3b4e52234620fc9f7657f9f8d56c96a2b7c7826ae6b57ebb2221a3fe154b03b6637cea7e6d98e3e45d87cf8dc432f723d3d71f89c5192ac8d7290684d2c25ce55846a80c9a7823f6acd9bb29fa6cd71f20bc90eccfca20451d0c976e460e672b000df49466408d527affe0303027a728f9feb3b864260abd761730327bca2aaa4ea0525c175e92bf240682a0e83b226f97ecb2e935b62c9a133858ce31b271fa8eb41f6a1b3cd72a63025ce1a75ee4180dcc284884904181102000905024dfcb16a021b0c000a091033af447ccd759b09dd0b009e3c3e7296092c81bee5a19929462caaf2fff3ae26009e218c437a2340e7ea628149af1ec98ec091a43992b00200009501e1044dfcb1be1104009f61faa61aa43df75d128cbe53de528c4aec49ce9360c992e70c77072ad5623de0a3a6212771b66b39a30dad6781799e92608316900518ec01184a85d872365b7d2ba4bacfb5882ea3c2473d3750dc6178cc1cf82147fb58caa28b28e9f12f6d1efcb0534abed644156c91cca4ab78834268495160b2400bc422beb37d237c2300a0cac94911b6d493bda1e1fbc6feeca7cb7421d34b03fe22cec6ccb39675bb7b94a335c2b7be888fd3906a1125f33301d8aa6ec6ee6878f46f73961c8d57a3e9544d8ef2a2cbfd4d52da665b1266928cfe4cb347a58c412815f3b2d2369dec04b41ac9a71cc9547426d5ab941cccf3b18575637ccfb42df1a802df3cfe0a999f9e7109331170e3a221991bf868543960f8c816c28097e503fe319db10fb98049f3a57d7c80c420da66d56f3644371631fad3f0ff4040a19a4fedc2d07727a1b27576f75a4d28c47d8246f27071e12d7a8de62aad216ddbae6aa02efd6b8a3e2818cda48526549791ab277e447b3a36c57cefe9b592f5eab73959743fcc8e83cbefec03a329b55018b53eec196765ae40ef9e20521a603c551efe0303020950d53a146bf9c66034d00c23130cce95576a2ff78016ca471276e8227fb30b1ffbd92e61804fb0c3eff9e30b1a826ee8f3e4730b4d86273ca977b4164453412f456c47616d616c2054657374204b65792032886204131102002205024dfcb1be021b03060b090807030206150802090a0b0416020301021e01021780000a0910a86bf526325b21b22bd9009e34511620415c974750a20df5cb56b182f3b48e6600a0a9466cb1a1305a84953445f77d461593f1d42bc1b00200009d0157044dfcb1be1004009565a951da1ee87119d600c077198f1c1bceb0f7aa54552489298e41ff788fa8f0d43a69871f0f6f77ebdfb14a4260cf9fbeb65d5844b4272a1904dd95136d06c3da745dc46327dd44a0f16f60135914368c8039a34033862261806bb2c5ce1152e2840254697872c85441ccb7321431d75a747a4bfb1d2c66362b51ce76311700030503fc0ea76601c196768070b7365a200e6ddb09307f262d5f39eec467b5f5784e22abdf1aa49226f59ab37cb49969d8f5230ea65caf56015abda62604544ed526c5c522bf92bed178a078789f6c807b6d34885688024a5bed9e9f8c58d11d4b82487b44c5f470c5606806a0443b79cadb45e0f897a561a53f724e5349b9267c75ca17fe0303020950d53a146bf9c660bc5f4ce8f072465e2d2466434320c1e712272fafc20e342fe7608101580fa1a1a367e60486a7cd1246b7ef5586cf5e10b32762b710a30144f12dd17dd4884904181102000905024dfcb1be021b0c000a0910a86bf526325b21b2904c00a0b2b66b4b39ccffda1d10f3ea8d58f827e30a8b8e009f4255b2d8112a184e40cde43a34e8655ca7809370b0020000"
+
+const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300"
+
+const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200"
+
+const signedEncryptedMessageHex = "848c032a67d68660df41c70103ff5789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8d2c03b018bd210b1d3791e1aba74b0f1034e122ab72e760492c192383cf5e20b5628bd043272d63df9b923f147eb6091cd897553204832aba48fec54aa447547bb16305a1024713b90e77fd0065f1918271947549205af3c74891af22ee0b56cd29bfec6d6e351901cd4ab3ece7c486f1e32a792d4e474aed98ee84b3f591c7dff37b64e0ecd68fd036d517e412dcadf85840ce184ad7921ad446c4ee28db80447aea1ca8d4f574db4d4e37688158ddd19e14ee2eab4873d46947d65d14a23e788d912cf9a19624ca7352469b72a83866b7c23cb5ace3deab3c7018061b0ba0f39ed2befe27163e5083cf9b8271e3e3d52cc7ad6e2a3bd81d4c3d7022f8d"
+
+const signedEncryptedMessage2Hex = "85010e03cf6a7abcd43e36731003fb057f5495b79db367e277cdbe4ab90d924ddee0c0381494112ff8c1238fb0184af35d1731573b01bc4c55ecacd2aafbe2003d36310487d1ecc9ac994f3fada7f9f7f5c3a64248ab7782906c82c6ff1303b69a84d9a9529c31ecafbcdb9ba87e05439897d87e8a2a3dec55e14df19bba7f7bd316291c002ae2efd24f83f9e3441203fc081c0c23dc3092a454ca8a082b27f631abf73aca341686982e8fbda7e0e7d863941d68f3de4a755c2964407f4b5e0477b3196b8c93d551dd23c8beef7d0f03fbb1b6066f78907faf4bf1677d8fcec72651124080e0b7feae6b476e72ab207d38d90b958759fdedfc3c6c35717c9dbfc979b3cfbbff0a76d24a5e57056bb88acbd2a901ef64bc6e4db02adc05b6250ff378de81dca18c1910ab257dff1b9771b85bb9bbe0a69f5989e6d1710a35e6dfcceb7d8fb5ccea8db3932b3d9ff3fe0d327597c68b3622aec8e3716c83a6c93f497543b459b58ba504ed6bcaa747d37d2ca746fe49ae0a6ce4a8b694234e941b5159ff8bd34b9023da2814076163b86f40eed7c9472f81b551452d5ab87004a373c0172ec87ea6ce42ccfa7dbdad66b745496c4873d8019e8c28d6b3"
+
+const symmetricallyEncryptedCompressedHex = "8c0d04030302eb4a03808145d0d260c92f714339e13de5a79881216431925bf67ee2898ea61815f07894cd0703c50d0a76ef64d482196f47a8bc729af9b80bb6"
+
+const dsaTestKeyHex = "9901a2044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794"
+
+const dsaTestKeyPrivateHex = "9501bb044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4d00009f592e0619d823953577d4503061706843317e4fee083db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794"
+
+const armoredPrivateKeyBlock = `-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp
+idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn
+vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB
+AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X
+0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL
+IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk
+VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn
+gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9
+TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx
+q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz
+dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA
+CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1
+ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+
+eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid
+AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV
+bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK
+/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA
+A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX
+TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc
+lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6
+rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN
+oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8
+QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU
+nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC
+AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp
+BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad
+AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL
+VrM0m72/jnpKo04=
+=zNCn
+-----END PGP PRIVATE KEY BLOCK-----`
+
+const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003`
diff --git a/openpgp/s2k/s2k.go b/openpgp/s2k/s2k.go
new file mode 100644
index 0000000..c8b9bbd
--- /dev/null
+++ b/openpgp/s2k/s2k.go
@@ -0,0 +1,183 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package s2k implements the various OpenPGP string-to-key transforms as
+// specified in RFC 4800 section 3.7.1.
+package s2k
+
+import (
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "crypto"
+ "hash"
+ "io"
+ "strconv"
+)
+
+// Simple writes to out the result of computing the Simple S2K function (RFC
+// 4880, section 3.7.1.1) using the given hash and input passphrase.
+func Simple(out []byte, h hash.Hash, in []byte) {
+ Salted(out, h, in, nil)
+}
+
+var zero [1]byte
+
+// Salted writes to out the result of computing the Salted S2K function (RFC
+// 4880, section 3.7.1.2) using the given hash, input passphrase and salt.
+func Salted(out []byte, h hash.Hash, in []byte, salt []byte) {
+ done := 0
+ var digest []byte
+
+ for i := 0; done < len(out); i++ {
+ h.Reset()
+ for j := 0; j < i; j++ {
+ h.Write(zero[:])
+ }
+ h.Write(salt)
+ h.Write(in)
+ digest = h.Sum(digest[:0])
+ n := copy(out[done:], digest)
+ done += n
+ }
+}
+
+// Iterated writes to out the result of computing the Iterated and Salted S2K
+// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase,
+// salt and iteration count.
+func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
+ combined := make([]byte, len(in)+len(salt))
+ copy(combined, salt)
+ copy(combined[len(salt):], in)
+
+ if count < len(combined) {
+ count = len(combined)
+ }
+
+ done := 0
+ var digest []byte
+ for i := 0; done < len(out); i++ {
+ h.Reset()
+ for j := 0; j < i; j++ {
+ h.Write(zero[:])
+ }
+ written := 0
+ for written < count {
+ if written+len(combined) > count {
+ todo := count - written
+ h.Write(combined[:todo])
+ written = count
+ } else {
+ h.Write(combined)
+ written += len(combined)
+ }
+ }
+ digest = h.Sum(digest[:0])
+ n := copy(out[done:], digest)
+ done += n
+ }
+}
+
+// Parse reads a binary specification for a string-to-key transformation from r
+// and returns a function which performs that transform.
+func Parse(r io.Reader) (f func(out, in []byte), err error) {
+ var buf [9]byte
+
+ _, err = io.ReadFull(r, buf[:2])
+ if err != nil {
+ return
+ }
+
+ hash, ok := HashIdToHash(buf[1])
+ if !ok {
+ return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1])))
+ }
+ h := hash.New()
+ if h == nil {
+ return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hash)))
+ }
+
+ switch buf[0] {
+ case 1:
+ f := func(out, in []byte) {
+ Simple(out, h, in)
+ }
+ return f, nil
+ case 2:
+ _, err = io.ReadFull(r, buf[:8])
+ if err != nil {
+ return
+ }
+ f := func(out, in []byte) {
+ Salted(out, h, in, buf[:8])
+ }
+ return f, nil
+ case 3:
+ _, err = io.ReadFull(r, buf[:9])
+ if err != nil {
+ return
+ }
+ count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6)
+ f := func(out, in []byte) {
+ Iterated(out, h, in, buf[:8], count)
+ }
+ return f, nil
+ }
+
+ return nil, errors.UnsupportedError("S2K function")
+}
+
+// Serialize salts and stretches the given passphrase and writes the resulting
+// key into key. It also serializes an S2K descriptor to w.
+func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte) error {
+ var buf [11]byte
+ buf[0] = 3 /* iterated and salted */
+ buf[1], _ = HashToHashId(crypto.SHA1)
+ salt := buf[2:10]
+ if _, err := io.ReadFull(rand, salt); err != nil {
+ return err
+ }
+ const count = 65536 // this is the default in gpg
+ buf[10] = 96 // 65536 iterations
+ if _, err := w.Write(buf[:]); err != nil {
+ return err
+ }
+
+ Iterated(key, crypto.SHA1.New(), passphrase, salt, count)
+ return nil
+}
+
+// hashToHashIdMapping contains pairs relating OpenPGP's hash identifier with
+// Go's crypto.Hash type. See RFC 4880, section 9.4.
+var hashToHashIdMapping = []struct {
+ id byte
+ hash crypto.Hash
+}{
+ {1, crypto.MD5},
+ {2, crypto.SHA1},
+ {3, crypto.RIPEMD160},
+ {8, crypto.SHA256},
+ {9, crypto.SHA384},
+ {10, crypto.SHA512},
+ {11, crypto.SHA224},
+}
+
+// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
+// hash id.
+func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
+ for _, m := range hashToHashIdMapping {
+ if m.id == id {
+ return m.hash, true
+ }
+ }
+ return 0, false
+}
+
+// HashIdToHash returns an OpenPGP hash id which corresponds the given Hash.
+func HashToHashId(h crypto.Hash) (id byte, ok bool) {
+ for _, m := range hashToHashIdMapping {
+ if m.hash == h {
+ return m.id, true
+ }
+ }
+ return 0, false
+}
diff --git a/openpgp/s2k/s2k_test.go b/openpgp/s2k/s2k_test.go
new file mode 100644
index 0000000..3a094a1
--- /dev/null
+++ b/openpgp/s2k/s2k_test.go
@@ -0,0 +1,118 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package s2k
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/sha1"
+ "encoding/hex"
+ "testing"
+)
+
+var saltedTests = []struct {
+ in, out string
+}{
+ {"hello", "10295ac1"},
+ {"world", "ac587a5e"},
+ {"foo", "4dda8077"},
+ {"bar", "bd8aac6b9ea9cae04eae6a91c6133b58b5d9a61c14f355516ed9370456"},
+ {"x", "f1d3f289"},
+ {"xxxxxxxxxxxxxxxxxxxxxxx", "e00d7b45"},
+}
+
+func TestSalted(t *testing.T) {
+ h := sha1.New()
+ salt := [4]byte{1, 2, 3, 4}
+
+ for i, test := range saltedTests {
+ expected, _ := hex.DecodeString(test.out)
+ out := make([]byte, len(expected))
+ Salted(out, h, []byte(test.in), salt[:])
+ if !bytes.Equal(expected, out) {
+ t.Errorf("#%d, got: %x want: %x", i, out, expected)
+ }
+ }
+}
+
+var iteratedTests = []struct {
+ in, out string
+}{
+ {"hello", "83126105"},
+ {"world", "6fa317f9"},
+ {"foo", "8fbc35b9"},
+ {"bar", "2af5a99b54f093789fd657f19bd245af7604d0f6ae06f66602a46a08ae"},
+ {"x", "5a684dfe"},
+ {"xxxxxxxxxxxxxxxxxxxxxxx", "18955174"},
+}
+
+func TestIterated(t *testing.T) {
+ h := sha1.New()
+ salt := [4]byte{4, 3, 2, 1}
+
+ for i, test := range iteratedTests {
+ expected, _ := hex.DecodeString(test.out)
+ out := make([]byte, len(expected))
+ Iterated(out, h, []byte(test.in), salt[:], 31)
+ if !bytes.Equal(expected, out) {
+ t.Errorf("#%d, got: %x want: %x", i, out, expected)
+ }
+ }
+}
+
+var parseTests = []struct {
+ spec, in, out string
+}{
+ /* Simple with SHA1 */
+ {"0102", "hello", "aaf4c61d"},
+ /* Salted with SHA1 */
+ {"02020102030405060708", "hello", "f4f7d67e"},
+ /* Iterated with SHA1 */
+ {"03020102030405060708f1", "hello", "f2a57b7c"},
+}
+
+func TestParse(t *testing.T) {
+ for i, test := range parseTests {
+ spec, _ := hex.DecodeString(test.spec)
+ buf := bytes.NewBuffer(spec)
+ f, err := Parse(buf)
+ if err != nil {
+ t.Errorf("%d: Parse returned error: %s", i, err)
+ continue
+ }
+
+ expected, _ := hex.DecodeString(test.out)
+ out := make([]byte, len(expected))
+ f(out, []byte(test.in))
+ if !bytes.Equal(out, expected) {
+ t.Errorf("%d: output got: %x want: %x", i, out, expected)
+ }
+ if testing.Short() {
+ break
+ }
+ }
+}
+
+func TestSerialize(t *testing.T) {
+ buf := bytes.NewBuffer(nil)
+ key := make([]byte, 16)
+ passphrase := []byte("testing")
+ err := Serialize(buf, key, rand.Reader, passphrase)
+ if err != nil {
+ t.Errorf("failed to serialize: %s", err)
+ return
+ }
+
+ f, err := Parse(buf)
+ if err != nil {
+ t.Errorf("failed to reparse: %s", err)
+ return
+ }
+ key2 := make([]byte, len(key))
+ f(key2, passphrase)
+ if !bytes.Equal(key2, key) {
+ t.Errorf("keys don't match: %x (serialied) vs %x (parsed)", key, key2)
+ }
+}
diff --git a/openpgp/write.go b/openpgp/write.go
new file mode 100644
index 0000000..c93b26e
--- /dev/null
+++ b/openpgp/write.go
@@ -0,0 +1,315 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+ "code.google.com/p/go.crypto/openpgp/armor"
+ "code.google.com/p/go.crypto/openpgp/errors"
+ "code.google.com/p/go.crypto/openpgp/packet"
+ "code.google.com/p/go.crypto/openpgp/s2k"
+ "crypto"
+ "crypto/rand"
+ _ "crypto/sha256"
+ "hash"
+ "io"
+ "strconv"
+ "time"
+)
+
+// DetachSign signs message with the private key from signer (which must
+// already have been decrypted) and writes the signature to w.
+func DetachSign(w io.Writer, signer *Entity, message io.Reader) error {
+ return detachSign(w, signer, message, packet.SigTypeBinary)
+}
+
+// ArmoredDetachSign signs message with the private key from signer (which
+// must already have been decrypted) and writes an armored signature to w.
+func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader) (err error) {
+ return armoredDetachSign(w, signer, message, packet.SigTypeBinary)
+}
+
+// DetachSignText signs message (after canonicalising the line endings) with
+// the private key from signer (which must already have been decrypted) and
+// writes the signature to w.
+func DetachSignText(w io.Writer, signer *Entity, message io.Reader) error {
+ return detachSign(w, signer, message, packet.SigTypeText)
+}
+
+// ArmoredDetachSignText signs message (after canonicalising the line endings)
+// with the private key from signer (which must already have been decrypted)
+// and writes an armored signature to w.
+func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader) error {
+ return armoredDetachSign(w, signer, message, packet.SigTypeText)
+}
+
+func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType) (err error) {
+ out, err := armor.Encode(w, SignatureType, nil)
+ if err != nil {
+ return
+ }
+ err = detachSign(out, signer, message, sigType)
+ if err != nil {
+ return
+ }
+ return out.Close()
+}
+
+func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType) (err error) {
+ if signer.PrivateKey == nil {
+ return errors.InvalidArgumentError("signing key doesn't have a private key")
+ }
+ if signer.PrivateKey.Encrypted {
+ return errors.InvalidArgumentError("signing key is encrypted")
+ }
+
+ sig := new(packet.Signature)
+ sig.SigType = sigType
+ sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo
+ sig.Hash = crypto.SHA256
+ sig.CreationTime = time.Now()
+ sig.IssuerKeyId = &signer.PrivateKey.KeyId
+
+ h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
+ if err != nil {
+ return
+ }
+ io.Copy(wrappedHash, message)
+
+ err = sig.Sign(rand.Reader, h, signer.PrivateKey)
+ if err != nil {
+ return
+ }
+
+ return sig.Serialize(w)
+}
+
+// FileHints contains metadata about encrypted files. This metadata is, itself,
+// encrypted.
+type FileHints struct {
+ // IsBinary can be set to hint that the contents are binary data.
+ IsBinary bool
+ // FileName hints at the name of the file that should be written. It's
+ // truncated to 255 bytes if longer. It may be empty to suggest that the
+ // file should not be written to disk. It may be equal to "_CONSOLE" to
+ // suggest the data should not be written to disk.
+ FileName string
+ // ModTime contains the modification time of the file, or the zero time if not applicable.
+ ModTime time.Time
+}
+
+// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
+// The resulting WriteCloser must be closed after the contents of the file have
+// been written.
+func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints) (plaintext io.WriteCloser, err error) {
+ if hints == nil {
+ hints = &FileHints{}
+ }
+
+ key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, rand.Reader, passphrase, packet.CipherAES128)
+ if err != nil {
+ return
+ }
+ w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, rand.Reader, packet.CipherAES128, key)
+ if err != nil {
+ return
+ }
+ var epochSeconds uint32
+ if !hints.ModTime.IsZero() {
+ epochSeconds = uint32(hints.ModTime.Unix())
+ }
+ return packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
+}
+
+// intersectPreferences mutates and returns a prefix of a that contains only
+// the values in the intersection of a and b. The order of a is preserved.
+func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
+ var j int
+ for _, v := range a {
+ for _, v2 := range b {
+ if v == v2 {
+ a[j] = v
+ j++
+ break
+ }
+ }
+ }
+
+ return a[:j]
+}
+
+func hashToHashId(h crypto.Hash) uint8 {
+ v, ok := s2k.HashToHashId(h)
+ if !ok {
+ panic("tried to convert unknown hash")
+ }
+ return v
+}
+
+// Encrypt encrypts a message to a number of recipients and, optionally, signs
+// it. hints contains optional information, that is also encrypted, that aids
+// the recipients in processing the message. The resulting WriteCloser must
+// be closed after the contents of the file have been written.
+func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints) (plaintext io.WriteCloser, err error) {
+ var signer *packet.PrivateKey
+ if signed != nil {
+ signer = signed.signingKey().PrivateKey
+ if signer == nil || signer.Encrypted {
+ return nil, errors.InvalidArgumentError("signing key must be decrypted")
+ }
+ }
+
+ // These are the possible ciphers that we'll use for the message.
+ candidateCiphers := []uint8{
+ uint8(packet.CipherAES128),
+ uint8(packet.CipherAES256),
+ uint8(packet.CipherCAST5),
+ }
+ // These are the possible hash functions that we'll use for the signature.
+ candidateHashes := []uint8{
+ hashToHashId(crypto.SHA256),
+ hashToHashId(crypto.SHA512),
+ hashToHashId(crypto.SHA1),
+ hashToHashId(crypto.RIPEMD160),
+ }
+ // In the event that a recipient doesn't specify any supported ciphers
+ // or hash functions, these are the ones that we assume that every
+ // implementation supports.
+ defaultCiphers := candidateCiphers[len(candidateCiphers)-1:]
+ defaultHashes := candidateHashes[len(candidateHashes)-1:]
+
+ encryptKeys := make([]Key, len(to))
+ for i := range to {
+ encryptKeys[i] = to[i].encryptionKey()
+ if encryptKeys[i].PublicKey == nil {
+ return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys")
+ }
+
+ sig := to[i].primaryIdentity().SelfSignature
+
+ preferredSymmetric := sig.PreferredSymmetric
+ if len(preferredSymmetric) == 0 {
+ preferredSymmetric = defaultCiphers
+ }
+ preferredHashes := sig.PreferredHash
+ if len(preferredHashes) == 0 {
+ preferredHashes = defaultHashes
+ }
+ candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric)
+ candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
+ }
+
+ if len(candidateCiphers) == 0 || len(candidateHashes) == 0 {
+ return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms")
+ }
+
+ cipher := packet.CipherFunction(candidateCiphers[0])
+ hash, _ := s2k.HashIdToHash(candidateHashes[0])
+ symKey := make([]byte, cipher.KeySize())
+ if _, err := io.ReadFull(rand.Reader, symKey); err != nil {
+ return nil, err
+ }
+
+ for _, key := range encryptKeys {
+ if err := packet.SerializeEncryptedKey(ciphertext, rand.Reader, key.PublicKey, cipher, symKey); err != nil {
+ return nil, err
+ }
+ }
+
+ encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, rand.Reader, cipher, symKey)
+ if err != nil {
+ return
+ }
+
+ if signer != nil {
+ ops := &packet.OnePassSignature{
+ SigType: packet.SigTypeBinary,
+ Hash: hash,
+ PubKeyAlgo: signer.PubKeyAlgo,
+ KeyId: signer.KeyId,
+ IsLast: true,
+ }
+ if err := ops.Serialize(encryptedData); err != nil {
+ return nil, err
+ }
+ }
+
+ if hints == nil {
+ hints = &FileHints{}
+ }
+
+ w := encryptedData
+ if signer != nil {
+ // If we need to write a signature packet after the literal
+ // data then we need to stop literalData from closing
+ // encryptedData.
+ w = noOpCloser{encryptedData}
+
+ }
+ var epochSeconds uint32
+ if !hints.ModTime.IsZero() {
+ epochSeconds = uint32(hints.ModTime.Unix())
+ }
+ literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
+ if err != nil {
+ return nil, err
+ }
+
+ if signer != nil {
+ return signatureWriter{encryptedData, literalData, hash, hash.New(), signer}, nil
+ }
+ return literalData, nil
+}
+
+// signatureWriter hashes the contents of a message while passing it along to
+// literalData. When closed, it closes literalData, writes a signature packet
+// to encryptedData and then also closes encryptedData.
+type signatureWriter struct {
+ encryptedData io.WriteCloser
+ literalData io.WriteCloser
+ hashType crypto.Hash
+ h hash.Hash
+ signer *packet.PrivateKey
+}
+
+func (s signatureWriter) Write(data []byte) (int, error) {
+ s.h.Write(data)
+ return s.literalData.Write(data)
+}
+
+func (s signatureWriter) Close() error {
+ sig := &packet.Signature{
+ SigType: packet.SigTypeBinary,
+ PubKeyAlgo: s.signer.PubKeyAlgo,
+ Hash: s.hashType,
+ CreationTime: time.Now(),
+ IssuerKeyId: &s.signer.KeyId,
+ }
+
+ if err := sig.Sign(rand.Reader, s.h, s.signer); err != nil {
+ return err
+ }
+ if err := s.literalData.Close(); err != nil {
+ return err
+ }
+ if err := sig.Serialize(s.encryptedData); err != nil {
+ return err
+ }
+ return s.encryptedData.Close()
+}
+
+// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
+// TODO: we have two of these in OpenPGP packages alone. This probably needs
+// to be promoted somewhere more common.
+type noOpCloser struct {
+ w io.Writer
+}
+
+func (c noOpCloser) Write(data []byte) (n int, err error) {
+ return c.w.Write(data)
+}
+
+func (c noOpCloser) Close() error {
+ return nil
+}
diff --git a/openpgp/write_test.go b/openpgp/write_test.go
new file mode 100644
index 0000000..7df02e7
--- /dev/null
+++ b/openpgp/write_test.go
@@ -0,0 +1,232 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+ "bytes"
+ "crypto/rand"
+ "io"
+ "io/ioutil"
+ "testing"
+ "time"
+)
+
+func TestSignDetached(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ out := bytes.NewBuffer(nil)
+ message := bytes.NewBufferString(signedInput)
+ err := DetachSign(out, kring[0], message)
+ if err != nil {
+ t.Error(err)
+ }
+
+ testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId)
+}
+
+func TestSignTextDetached(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ out := bytes.NewBuffer(nil)
+ message := bytes.NewBufferString(signedInput)
+ err := DetachSignText(out, kring[0], message)
+ if err != nil {
+ t.Error(err)
+ }
+
+ testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId)
+}
+
+func TestSignDetachedDSA(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyPrivateHex))
+ out := bytes.NewBuffer(nil)
+ message := bytes.NewBufferString(signedInput)
+ err := DetachSign(out, kring[0], message)
+ if err != nil {
+ t.Error(err)
+ }
+
+ testDetachedSignature(t, kring, out, signedInput, "check", testKey3KeyId)
+}
+
+func TestNewEntity(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+
+ e, err := NewEntity(rand.Reader, time.Now(), "Test User", "test", "test@example.com")
+ if err != nil {
+ t.Errorf("failed to create entity: %s", err)
+ return
+ }
+
+ w := bytes.NewBuffer(nil)
+ if err := e.SerializePrivate(w); err != nil {
+ t.Errorf("failed to serialize entity: %s", err)
+ return
+ }
+ serialized := w.Bytes()
+
+ el, err := ReadKeyRing(w)
+ if err != nil {
+ t.Errorf("failed to reparse entity: %s", err)
+ return
+ }
+
+ if len(el) != 1 {
+ t.Errorf("wrong number of entities found, got %d, want 1", len(el))
+ }
+
+ w = bytes.NewBuffer(nil)
+ if err := e.SerializePrivate(w); err != nil {
+ t.Errorf("failed to serialize entity second time: %s", err)
+ return
+ }
+
+ if !bytes.Equal(w.Bytes(), serialized) {
+ t.Errorf("results differed")
+ }
+}
+
+func TestSymmetricEncryption(t *testing.T) {
+ buf := new(bytes.Buffer)
+ plaintext, err := SymmetricallyEncrypt(buf, []byte("testing"), nil)
+ if err != nil {
+ t.Errorf("error writing headers: %s", err)
+ return
+ }
+ message := []byte("hello world\n")
+ _, err = plaintext.Write(message)
+ if err != nil {
+ t.Errorf("error writing to plaintext writer: %s", err)
+ }
+ err = plaintext.Close()
+ if err != nil {
+ t.Errorf("error closing plaintext writer: %s", err)
+ }
+
+ md, err := ReadMessage(buf, nil, func(keys []Key, symmetric bool) ([]byte, error) {
+ return []byte("testing"), nil
+ })
+ if err != nil {
+ t.Errorf("error rereading message: %s", err)
+ }
+ messageBuf := bytes.NewBuffer(nil)
+ _, err = io.Copy(messageBuf, md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("error rereading message: %s", err)
+ }
+ if !bytes.Equal(message, messageBuf.Bytes()) {
+ t.Errorf("recovered message incorrect got '%s', want '%s'", messageBuf.Bytes(), message)
+ }
+}
+
+var testEncryptionTests = []struct {
+ keyRingHex string
+ isSigned bool
+}{
+ {
+ testKeys1And2PrivateHex,
+ false,
+ },
+ {
+ testKeys1And2PrivateHex,
+ true,
+ },
+ {
+ dsaElGamalTestKeysHex,
+ false,
+ },
+ {
+ dsaElGamalTestKeysHex,
+ true,
+ },
+}
+
+func TestEncryption(t *testing.T) {
+ for i, test := range testEncryptionTests {
+ kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
+
+ passphrase := []byte("passphrase")
+ for _, entity := range kring {
+ if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
+ err := entity.PrivateKey.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt key", i)
+ }
+ }
+ for _, subkey := range entity.Subkeys {
+ if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted {
+ err := subkey.PrivateKey.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt subkey", i)
+ }
+ }
+ }
+ }
+
+ var signed *Entity
+ if test.isSigned {
+ signed = kring[0]
+ }
+
+ buf := new(bytes.Buffer)
+ w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */ )
+ if err != nil {
+ t.Errorf("#%d: error in Encrypt: %s", i, err)
+ continue
+ }
+
+ const message = "testing"
+ _, err = w.Write([]byte(message))
+ if err != nil {
+ t.Errorf("#%d: error writing plaintext: %s", i, err)
+ continue
+ }
+ err = w.Close()
+ if err != nil {
+ t.Errorf("#%d: error closing WriteCloser: %s", i, err)
+ continue
+ }
+
+ md, err := ReadMessage(buf, kring, nil /* no prompt */ )
+ if err != nil {
+ t.Errorf("#%d: error reading message: %s", i, err)
+ continue
+ }
+
+ if test.isSigned {
+ expectedKeyId := kring[0].signingKey().PublicKey.KeyId
+ if md.SignedByKeyId != expectedKeyId {
+ t.Errorf("#%d: message signed by wrong key id, got: %d, want: %d", i, *md.SignedBy, expectedKeyId)
+ }
+ if md.SignedBy == nil {
+ t.Errorf("#%d: failed to find the signing Entity", i)
+ }
+ }
+
+ plaintext, err := ioutil.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("#%d: error reading encrypted contents: %s", i, err)
+ continue
+ }
+
+ expectedKeyId := kring[0].encryptionKey().PublicKey.KeyId
+ if len(md.EncryptedToKeyIds) != 1 || md.EncryptedToKeyIds[0] != expectedKeyId {
+ t.Errorf("#%d: expected message to be encrypted to %v, but got %#v", i, expectedKeyId, md.EncryptedToKeyIds)
+ }
+
+ if string(plaintext) != message {
+ t.Errorf("#%d: got: %s, want: %s", i, string(plaintext), message)
+ }
+
+ if test.isSigned {
+ if md.SignatureError != nil {
+ t.Errorf("#%d: signature error: %s", i, md.SignatureError)
+ }
+ if md.Signature == nil {
+ t.Error("signature missing")
+ }
+ }
+ }
+}
diff --git a/ripemd160/ripemd160.go b/ripemd160/ripemd160.go
new file mode 100644
index 0000000..da690f0
--- /dev/null
+++ b/ripemd160/ripemd160.go
@@ -0,0 +1,120 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ripemd160 implements the RIPEMD-160 hash algorithm.
+package ripemd160
+
+// RIPEMD-160 is designed by by Hans Dobbertin, Antoon Bosselaers, and Bart
+// Preneel with specifications available at:
+// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf.
+
+import (
+ "crypto"
+ "hash"
+)
+
+func init() {
+ crypto.RegisterHash(crypto.RIPEMD160, New)
+}
+
+// The size of the checksum in bytes.
+const Size = 20
+
+// The block size of the hash algorithm in bytes.
+const BlockSize = 64
+
+const (
+ _s0 = 0x67452301
+ _s1 = 0xefcdab89
+ _s2 = 0x98badcfe
+ _s3 = 0x10325476
+ _s4 = 0xc3d2e1f0
+)
+
+// digest represents the partial evaluation of a checksum.
+type digest struct {
+ s [5]uint32 // running context
+ x [BlockSize]byte // temporary buffer
+ nx int // index into x
+ tc uint64 // total count of bytes processed
+}
+
+func (d *digest) Reset() {
+ d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4
+ d.nx = 0
+ d.tc = 0
+}
+
+// New returns a new hash.Hash computing the checksum.
+func New() hash.Hash {
+ result := new(digest)
+ result.Reset()
+ return result
+}
+
+func (d *digest) Size() int { return Size }
+
+func (d *digest) BlockSize() int { return BlockSize }
+
+func (d *digest) Write(p []byte) (nn int, err error) {
+ nn = len(p)
+ d.tc += uint64(nn)
+ if d.nx > 0 {
+ n := len(p)
+ if n > BlockSize-d.nx {
+ n = BlockSize - d.nx
+ }
+ for i := 0; i < n; i++ {
+ d.x[d.nx+i] = p[i]
+ }
+ d.nx += n
+ if d.nx == BlockSize {
+ _Block(d, d.x[0:])
+ d.nx = 0
+ }
+ p = p[n:]
+ }
+ n := _Block(d, p)
+ p = p[n:]
+ if len(p) > 0 {
+ d.nx = copy(d.x[:], p)
+ }
+ return
+}
+
+func (d0 *digest) Sum(in []byte) []byte {
+ // Make a copy of d0 so that caller can keep writing and summing.
+ d := *d0
+
+ // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
+ tc := d.tc
+ var tmp [64]byte
+ tmp[0] = 0x80
+ if tc%64 < 56 {
+ d.Write(tmp[0 : 56-tc%64])
+ } else {
+ d.Write(tmp[0 : 64+56-tc%64])
+ }
+
+ // Length in bits.
+ tc <<= 3
+ for i := uint(0); i < 8; i++ {
+ tmp[i] = byte(tc >> (8 * i))
+ }
+ d.Write(tmp[0:8])
+
+ if d.nx != 0 {
+ panic("d.nx != 0")
+ }
+
+ var digest [Size]byte
+ for i, s := range d.s {
+ digest[i*4] = byte(s)
+ digest[i*4+1] = byte(s >> 8)
+ digest[i*4+2] = byte(s >> 16)
+ digest[i*4+3] = byte(s >> 24)
+ }
+
+ return append(in, digest[:]...)
+}
diff --git a/ripemd160/ripemd160_test.go b/ripemd160/ripemd160_test.go
new file mode 100644
index 0000000..5df1b25
--- /dev/null
+++ b/ripemd160/ripemd160_test.go
@@ -0,0 +1,64 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ripemd160
+
+// Test vectors are from:
+// http://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+
+import (
+ "fmt"
+ "io"
+ "testing"
+)
+
+type mdTest struct {
+ out string
+ in string
+}
+
+var vectors = [...]mdTest{
+ {"9c1185a5c5e9fc54612808977ee8f548b2258d31", ""},
+ {"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", "a"},
+ {"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", "abc"},
+ {"5d0689ef49d2fae572b881b123a85ffa21595f36", "message digest"},
+ {"f71c27109c692c1b56bbdceb5b9d2865b3708dbc", "abcdefghijklmnopqrstuvwxyz"},
+ {"12a053384a9c0c88e405a06c27dcf49ada62eb2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+ {"b0e20b6e3116640286ed3a87a5713079b21f5189", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+ {"9b752e45573d4b39f4dbd3323cab82bf63326bfb", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+}
+
+func TestVectors(t *testing.T) {
+ for i := 0; i < len(vectors); i++ {
+ tv := vectors[i]
+ md := New()
+ for j := 0; j < 3; j++ {
+ if j < 2 {
+ io.WriteString(md, tv.in)
+ } else {
+ io.WriteString(md, tv.in[0:len(tv.in)/2])
+ md.Sum(nil)
+ io.WriteString(md, tv.in[len(tv.in)/2:])
+ }
+ s := fmt.Sprintf("%x", md.Sum(nil))
+ if s != tv.out {
+ t.Fatalf("RIPEMD-160[%d](%s) = %s, expected %s", j, tv.in, s, tv.out)
+ }
+ md.Reset()
+ }
+ }
+}
+
+func TestMillionA(t *testing.T) {
+ md := New()
+ for i := 0; i < 100000; i++ {
+ io.WriteString(md, "aaaaaaaaaa")
+ }
+ out := "52783243c1697bdbe16d37f97f68f08325dc1528"
+ s := fmt.Sprintf("%x", md.Sum(nil))
+ if s != out {
+ t.Fatalf("RIPEMD-160 (1 million 'a') = %s, expected %s", s, out)
+ }
+ md.Reset()
+}
diff --git a/ripemd160/ripemd160block.go b/ripemd160/ripemd160block.go
new file mode 100644
index 0000000..7bc8e6c
--- /dev/null
+++ b/ripemd160/ripemd160block.go
@@ -0,0 +1,161 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// RIPEMD-160 block step.
+// In its own file so that a faster assembly or C version
+// can be substituted easily.
+
+package ripemd160
+
+// work buffer indices and roll amounts for one line
+var _n = [80]uint{
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
+ 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
+ 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
+ 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13,
+}
+
+var _r = [80]uint{
+ 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
+ 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
+ 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
+ 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
+ 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6,
+}
+
+// same for the other parallel one
+var n_ = [80]uint{
+ 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
+ 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
+ 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
+ 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
+ 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11,
+}
+
+var r_ = [80]uint{
+ 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
+ 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
+ 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
+ 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
+ 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11,
+}
+
+func _Block(md *digest, p []byte) int {
+ n := 0
+ var x [16]uint32
+ var alpha, beta uint32
+ for len(p) >= BlockSize {
+ a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4]
+ aa, bb, cc, dd, ee := a, b, c, d, e
+ j := 0
+ for i := 0; i < 16; i++ {
+ x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
+ j += 4
+ }
+
+ // round 1
+ i := 0
+ for i < 16 {
+ alpha = a + (b ^ c ^ d) + x[_n[i]]
+ s := _r[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + e
+ beta = c<<10 | c>>22
+ a, b, c, d, e = e, alpha, b, beta, d
+
+ // parallel line
+ alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6
+ s = r_[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + ee
+ beta = cc<<10 | cc>>22
+ aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+ i++
+ }
+
+ // round 2
+ for i < 32 {
+ alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999
+ s := _r[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + e
+ beta = c<<10 | c>>22
+ a, b, c, d, e = e, alpha, b, beta, d
+
+ // parallel line
+ alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124
+ s = r_[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + ee
+ beta = cc<<10 | cc>>22
+ aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+ i++
+ }
+
+ // round 3
+ for i < 48 {
+ alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1
+ s := _r[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + e
+ beta = c<<10 | c>>22
+ a, b, c, d, e = e, alpha, b, beta, d
+
+ // parallel line
+ alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3
+ s = r_[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + ee
+ beta = cc<<10 | cc>>22
+ aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+ i++
+ }
+
+ // round 4
+ for i < 64 {
+ alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc
+ s := _r[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + e
+ beta = c<<10 | c>>22
+ a, b, c, d, e = e, alpha, b, beta, d
+
+ // parallel line
+ alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9
+ s = r_[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + ee
+ beta = cc<<10 | cc>>22
+ aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+ i++
+ }
+
+ // round 5
+ for i < 80 {
+ alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e
+ s := _r[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + e
+ beta = c<<10 | c>>22
+ a, b, c, d, e = e, alpha, b, beta, d
+
+ // parallel line
+ alpha = aa + (bb ^ cc ^ dd) + x[n_[i]]
+ s = r_[i]
+ alpha = (alpha<<s | alpha>>(32-s)) + ee
+ beta = cc<<10 | cc>>22
+ aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+ i++
+ }
+
+ // combine results
+ dd += c + md.s[1]
+ md.s[1] = md.s[2] + d + ee
+ md.s[2] = md.s[3] + e + aa
+ md.s[3] = md.s[4] + a + bb
+ md.s[4] = md.s[0] + b + cc
+ md.s[0] = dd
+
+ p = p[BlockSize:]
+ n += BlockSize
+ }
+ return n
+}
diff --git a/ssh/channel.go b/ssh/channel.go
new file mode 100644
index 0000000..9d75f37
--- /dev/null
+++ b/ssh/channel.go
@@ -0,0 +1,318 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "errors"
+ "io"
+ "sync"
+)
+
+// A Channel is an ordered, reliable, duplex stream that is multiplexed over an
+// SSH connection.
+type Channel interface {
+ // Accept accepts the channel creation request.
+ Accept() error
+ // Reject rejects the channel creation request. After calling this, no
+ // other methods on the Channel may be called. If they are then the
+ // peer is likely to signal a protocol error and drop the connection.
+ Reject(reason RejectionReason, message string) error
+
+ // Read may return a ChannelRequest as an error.
+ Read(data []byte) (int, error)
+ Write(data []byte) (int, error)
+ Close() error
+
+ // AckRequest either sends an ack or nack to the channel request.
+ AckRequest(ok bool) error
+
+ // ChannelType returns the type of the channel, as supplied by the
+ // client.
+ ChannelType() string
+ // ExtraData returns the arbitary payload for this channel, as supplied
+ // by the client. This data is specific to the channel type.
+ ExtraData() []byte
+}
+
+// ChannelRequest represents a request sent on a channel, outside of the normal
+// stream of bytes. It may result from calling Read on a Channel.
+type ChannelRequest struct {
+ Request string
+ WantReply bool
+ Payload []byte
+}
+
+func (c ChannelRequest) Error() string {
+ return "channel request received"
+}
+
+// RejectionReason is an enumeration used when rejecting channel creation
+// requests. See RFC 4254, section 5.1.
+type RejectionReason int
+
+const (
+ Prohibited RejectionReason = iota + 1
+ ConnectionFailed
+ UnknownChannelType
+ ResourceShortage
+)
+
+type channel struct {
+ // immutable once created
+ chanType string
+ extraData []byte
+
+ theyClosed bool
+ theySentEOF bool
+ weClosed bool
+ dead bool
+
+ serverConn *ServerConn
+ myId, theirId uint32
+ myWindow, theirWindow uint32
+ maxPacketSize uint32
+ err error
+
+ pendingRequests []ChannelRequest
+ pendingData []byte
+ head, length int
+
+ // This lock is inferior to serverConn.lock
+ lock sync.Mutex
+ cond *sync.Cond
+}
+
+func (c *channel) Accept() error {
+ c.serverConn.lock.Lock()
+ defer c.serverConn.lock.Unlock()
+
+ if c.serverConn.err != nil {
+ return c.serverConn.err
+ }
+
+ confirm := channelOpenConfirmMsg{
+ PeersId: c.theirId,
+ MyId: c.myId,
+ MyWindow: c.myWindow,
+ MaxPacketSize: c.maxPacketSize,
+ }
+ return c.serverConn.writePacket(marshal(msgChannelOpenConfirm, confirm))
+}
+
+func (c *channel) Reject(reason RejectionReason, message string) error {
+ c.serverConn.lock.Lock()
+ defer c.serverConn.lock.Unlock()
+
+ if c.serverConn.err != nil {
+ return c.serverConn.err
+ }
+
+ reject := channelOpenFailureMsg{
+ PeersId: c.theirId,
+ Reason: uint32(reason),
+ Message: message,
+ Language: "en",
+ }
+ return c.serverConn.writePacket(marshal(msgChannelOpenFailure, reject))
+}
+
+func (c *channel) handlePacket(packet interface{}) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ switch packet := packet.(type) {
+ case *channelRequestMsg:
+ req := ChannelRequest{
+ Request: packet.Request,
+ WantReply: packet.WantReply,
+ Payload: packet.RequestSpecificData,
+ }
+
+ c.pendingRequests = append(c.pendingRequests, req)
+ c.cond.Signal()
+ case *channelCloseMsg:
+ c.theyClosed = true
+ c.cond.Signal()
+ case *channelEOFMsg:
+ c.theySentEOF = true
+ c.cond.Signal()
+ default:
+ panic("unknown packet type")
+ }
+}
+
+func (c *channel) handleData(data []byte) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ // The other side should never send us more than our window.
+ if len(data)+c.length > len(c.pendingData) {
+ // TODO(agl): we should tear down the channel with a protocol
+ // error.
+ return
+ }
+
+ c.myWindow -= uint32(len(data))
+ for i := 0; i < 2; i++ {
+ tail := c.head + c.length
+ if tail > len(c.pendingData) {
+ tail -= len(c.pendingData)
+ }
+ n := copy(c.pendingData[tail:], data)
+ data = data[n:]
+ c.length += n
+ }
+
+ c.cond.Signal()
+}
+
+func (c *channel) Read(data []byte) (n int, err error) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if c.err != nil {
+ return 0, c.err
+ }
+
+ if c.myWindow <= uint32(len(c.pendingData))/2 {
+ packet := marshal(msgChannelWindowAdjust, windowAdjustMsg{
+ PeersId: c.theirId,
+ AdditionalBytes: uint32(len(c.pendingData)) - c.myWindow,
+ })
+ if err := c.serverConn.writePacket(packet); err != nil {
+ return 0, err
+ }
+ }
+
+ for {
+ if c.theySentEOF || c.theyClosed || c.dead {
+ return 0, io.EOF
+ }
+
+ if len(c.pendingRequests) > 0 {
+ req := c.pendingRequests[0]
+ if len(c.pendingRequests) == 1 {
+ c.pendingRequests = nil
+ } else {
+ oldPendingRequests := c.pendingRequests
+ c.pendingRequests = make([]ChannelRequest, len(oldPendingRequests)-1)
+ copy(c.pendingRequests, oldPendingRequests[1:])
+ }
+
+ return 0, req
+ }
+
+ if c.length > 0 {
+ tail := c.head + c.length
+ if tail > len(c.pendingData) {
+ tail -= len(c.pendingData)
+ }
+ n = copy(data, c.pendingData[c.head:tail])
+ c.head += n
+ c.length -= n
+ if c.head == len(c.pendingData) {
+ c.head = 0
+ }
+ return
+ }
+
+ c.cond.Wait()
+ }
+
+ panic("unreachable")
+}
+
+func (c *channel) Write(data []byte) (n int, err error) {
+ for len(data) > 0 {
+ c.lock.Lock()
+ if c.dead || c.weClosed {
+ return 0, io.EOF
+ }
+
+ if c.theirWindow == 0 {
+ c.cond.Wait()
+ continue
+ }
+ c.lock.Unlock()
+
+ todo := data
+ if uint32(len(todo)) > c.theirWindow {
+ todo = todo[:c.theirWindow]
+ }
+
+ packet := make([]byte, 1+4+4+len(todo))
+ packet[0] = msgChannelData
+ packet[1] = byte(c.theirId >> 24)
+ packet[2] = byte(c.theirId >> 16)
+ packet[3] = byte(c.theirId >> 8)
+ packet[4] = byte(c.theirId)
+ packet[5] = byte(len(todo) >> 24)
+ packet[6] = byte(len(todo) >> 16)
+ packet[7] = byte(len(todo) >> 8)
+ packet[8] = byte(len(todo))
+ copy(packet[9:], todo)
+
+ c.serverConn.lock.Lock()
+ if err = c.serverConn.writePacket(packet); err != nil {
+ c.serverConn.lock.Unlock()
+ return
+ }
+ c.serverConn.lock.Unlock()
+
+ n += len(todo)
+ data = data[len(todo):]
+ }
+
+ return
+}
+
+func (c *channel) Close() error {
+ c.serverConn.lock.Lock()
+ defer c.serverConn.lock.Unlock()
+
+ if c.serverConn.err != nil {
+ return c.serverConn.err
+ }
+
+ if c.weClosed {
+ return errors.New("ssh: channel already closed")
+ }
+ c.weClosed = true
+
+ closeMsg := channelCloseMsg{
+ PeersId: c.theirId,
+ }
+ return c.serverConn.writePacket(marshal(msgChannelClose, closeMsg))
+}
+
+func (c *channel) AckRequest(ok bool) error {
+ c.serverConn.lock.Lock()
+ defer c.serverConn.lock.Unlock()
+
+ if c.serverConn.err != nil {
+ return c.serverConn.err
+ }
+
+ if ok {
+ ack := channelRequestSuccessMsg{
+ PeersId: c.theirId,
+ }
+ return c.serverConn.writePacket(marshal(msgChannelSuccess, ack))
+ } else {
+ ack := channelRequestFailureMsg{
+ PeersId: c.theirId,
+ }
+ return c.serverConn.writePacket(marshal(msgChannelFailure, ack))
+ }
+ panic("unreachable")
+}
+
+func (c *channel) ChannelType() string {
+ return c.chanType
+}
+
+func (c *channel) ExtraData() []byte {
+ return c.extraData
+}
diff --git a/ssh/cipher.go b/ssh/cipher.go
new file mode 100644
index 0000000..d91929a
--- /dev/null
+++ b/ssh/cipher.go
@@ -0,0 +1,88 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rc4"
+)
+
+// streamDump is used to dump the initial keystream for stream ciphers. It is a
+// a write-only buffer, and not intended for reading so do not require a mutex.
+var streamDump [512]byte
+
+// noneCipher implements cipher.Stream and provides no encryption. It is used
+// by the transport before the first key-exchange.
+type noneCipher struct{}
+
+func (c noneCipher) XORKeyStream(dst, src []byte) {
+ copy(dst, src)
+}
+
+func newAESCTR(key, iv []byte) (cipher.Stream, error) {
+ c, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ return cipher.NewCTR(c, iv), nil
+}
+
+func newRC4(key, iv []byte) (cipher.Stream, error) {
+ return rc4.NewCipher(key)
+}
+
+type cipherMode struct {
+ keySize int
+ ivSize int
+ skip int
+ createFn func(key, iv []byte) (cipher.Stream, error)
+}
+
+func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
+ if len(key) < c.keySize {
+ panic("ssh: key length too small for cipher")
+ }
+ if len(iv) < c.ivSize {
+ panic("ssh: iv too small for cipher")
+ }
+
+ stream, err := c.createFn(key[:c.keySize], iv[:c.ivSize])
+ if err != nil {
+ return nil, err
+ }
+
+ for remainingToDump := c.skip; remainingToDump > 0; {
+ dumpThisTime := remainingToDump
+ if dumpThisTime > len(streamDump) {
+ dumpThisTime = len(streamDump)
+ }
+ stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
+ remainingToDump -= dumpThisTime
+ }
+
+ return stream, nil
+}
+
+// Specifies a default set of ciphers and a preference order. This is based on
+// OpenSSH's default client preference order, minus algorithms that are not
+// implemented.
+var DefaultCipherOrder = []string{
+ "aes128-ctr", "aes192-ctr", "aes256-ctr",
+ "arcfour256", "arcfour128",
+}
+
+var cipherModes = map[string]*cipherMode{
+ // Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
+ // are defined in the order specified in the RFC.
+ "aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
+ "aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
+ "aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
+
+ // Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
+ // They are defined in the order specified in the RFC.
+ "arcfour128": {16, 0, 1536, newRC4},
+ "arcfour256": {32, 0, 1536, newRC4},
+}
diff --git a/ssh/cipher_test.go b/ssh/cipher_test.go
new file mode 100644
index 0000000..ea27bd8
--- /dev/null
+++ b/ssh/cipher_test.go
@@ -0,0 +1,62 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "bytes"
+ "testing"
+)
+
+// TestCipherReversal tests that each cipher factory produces ciphers that can
+// encrypt and decrypt some data successfully.
+func TestCipherReversal(t *testing.T) {
+ testData := []byte("abcdefghijklmnopqrstuvwxyz012345")
+ testKey := []byte("AbCdEfGhIjKlMnOpQrStUvWxYz012345")
+ testIv := []byte("sdflkjhsadflkjhasdflkjhsadfklhsa")
+
+ cryptBuffer := make([]byte, 32)
+
+ for name, cipherMode := range cipherModes {
+ encrypter, err := cipherMode.createCipher(testKey, testIv)
+ if err != nil {
+ t.Errorf("failed to create encrypter for %q: %s", name, err)
+ continue
+ }
+ decrypter, err := cipherMode.createCipher(testKey, testIv)
+ if err != nil {
+ t.Errorf("failed to create decrypter for %q: %s", name, err)
+ continue
+ }
+
+ copy(cryptBuffer, testData)
+
+ encrypter.XORKeyStream(cryptBuffer, cryptBuffer)
+ if name == "none" {
+ if !bytes.Equal(cryptBuffer, testData) {
+ t.Errorf("encryption made change with 'none' cipher")
+ continue
+ }
+ } else {
+ if bytes.Equal(cryptBuffer, testData) {
+ t.Errorf("encryption made no change with %q", name)
+ continue
+ }
+ }
+
+ decrypter.XORKeyStream(cryptBuffer, cryptBuffer)
+ if !bytes.Equal(cryptBuffer, testData) {
+ t.Errorf("decrypted bytes not equal to input with %q", name)
+ continue
+ }
+ }
+}
+
+func TestDefaultCiphersExist(t *testing.T) {
+ for _, cipherAlgo := range DefaultCipherOrder {
+ if _, ok := cipherModes[cipherAlgo]; !ok {
+ t.Errorf("default cipher %q is unknown", cipherAlgo)
+ }
+ }
+}
diff --git a/ssh/client.go b/ssh/client.go
new file mode 100644
index 0000000..eb6c035
--- /dev/null
+++ b/ssh/client.go
@@ -0,0 +1,505 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "crypto"
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "net"
+ "sync"
+)
+
+// clientVersion is the fixed identification string that the client will use.
+var clientVersion = []byte("SSH-2.0-Go\r\n")
+
+// ClientConn represents the client side of an SSH connection.
+type ClientConn struct {
+ *transport
+ config *ClientConfig
+ chanlist
+}
+
+// Client returns a new SSH client connection using c as the underlying transport.
+func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
+ conn := &ClientConn{
+ transport: newTransport(c, config.rand()),
+ config: config,
+ }
+ if err := conn.handshake(); err != nil {
+ conn.Close()
+ return nil, err
+ }
+ go conn.mainLoop()
+ return conn, nil
+}
+
+// handshake performs the client side key exchange. See RFC 4253 Section 7.
+func (c *ClientConn) handshake() error {
+ var magics handshakeMagics
+
+ if _, err := c.Write(clientVersion); err != nil {
+ return err
+ }
+ if err := c.Flush(); err != nil {
+ return err
+ }
+ magics.clientVersion = clientVersion[:len(clientVersion)-2]
+
+ // read remote server version
+ version, err := readVersion(c)
+ if err != nil {
+ return err
+ }
+ magics.serverVersion = version
+ clientKexInit := kexInitMsg{
+ KexAlgos: supportedKexAlgos,
+ ServerHostKeyAlgos: supportedHostKeyAlgos,
+ CiphersClientServer: c.config.Crypto.ciphers(),
+ CiphersServerClient: c.config.Crypto.ciphers(),
+ MACsClientServer: supportedMACs,
+ MACsServerClient: supportedMACs,
+ CompressionClientServer: supportedCompressions,
+ CompressionServerClient: supportedCompressions,
+ }
+ kexInitPacket := marshal(msgKexInit, clientKexInit)
+ magics.clientKexInit = kexInitPacket
+
+ if err := c.writePacket(kexInitPacket); err != nil {
+ return err
+ }
+ packet, err := c.readPacket()
+ if err != nil {
+ return err
+ }
+
+ magics.serverKexInit = packet
+
+ var serverKexInit kexInitMsg
+ if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
+ return err
+ }
+
+ kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(c.transport, &clientKexInit, &serverKexInit)
+ if !ok {
+ return errors.New("ssh: no common algorithms")
+ }
+
+ if serverKexInit.FirstKexFollows && kexAlgo != serverKexInit.KexAlgos[0] {
+ // The server sent a Kex message for the wrong algorithm,
+ // which we have to ignore.
+ if _, err := c.readPacket(); err != nil {
+ return err
+ }
+ }
+
+ var H, K []byte
+ var hashFunc crypto.Hash
+ switch kexAlgo {
+ case kexAlgoDH14SHA1:
+ hashFunc = crypto.SHA1
+ dhGroup14Once.Do(initDHGroup14)
+ H, K, err = c.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
+ default:
+ err = fmt.Errorf("ssh: unexpected key exchange algorithm %v", kexAlgo)
+ }
+ if err != nil {
+ return err
+ }
+
+ if err = c.writePacket([]byte{msgNewKeys}); err != nil {
+ return err
+ }
+ if err = c.transport.writer.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
+ return err
+ }
+ if packet, err = c.readPacket(); err != nil {
+ return err
+ }
+ if packet[0] != msgNewKeys {
+ return UnexpectedMessageError{msgNewKeys, packet[0]}
+ }
+ if err := c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
+ return err
+ }
+ return c.authenticate(H)
+}
+
+// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
+// returned values are given the same names as in RFC 4253, section 8.
+func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) ([]byte, []byte, error) {
+ x, err := rand.Int(c.config.rand(), group.p)
+ if err != nil {
+ return nil, nil, err
+ }
+ X := new(big.Int).Exp(group.g, x, group.p)
+ kexDHInit := kexDHInitMsg{
+ X: X,
+ }
+ if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil {
+ return nil, nil, err
+ }
+
+ packet, err := c.readPacket()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var kexDHReply = new(kexDHReplyMsg)
+ if err = unmarshal(kexDHReply, packet, msgKexDHReply); err != nil {
+ return nil, nil, err
+ }
+
+ if kexDHReply.Y.Sign() == 0 || kexDHReply.Y.Cmp(group.p) >= 0 {
+ return nil, nil, errors.New("server DH parameter out of bounds")
+ }
+
+ kInt := new(big.Int).Exp(kexDHReply.Y, x, group.p)
+ h := hashFunc.New()
+ writeString(h, magics.clientVersion)
+ writeString(h, magics.serverVersion)
+ writeString(h, magics.clientKexInit)
+ writeString(h, magics.serverKexInit)
+ writeString(h, kexDHReply.HostKey)
+ writeInt(h, X)
+ writeInt(h, kexDHReply.Y)
+ K := make([]byte, intLength(kInt))
+ marshalInt(K, kInt)
+ h.Write(K)
+
+ H := h.Sum(nil)
+
+ return H, K, nil
+}
+
+// mainLoop reads incoming messages and routes channel messages
+// to their respective ClientChans.
+func (c *ClientConn) mainLoop() {
+ // TODO(dfc) signal the underlying close to all channels
+ defer c.Close()
+ for {
+ packet, err := c.readPacket()
+ if err != nil {
+ break
+ }
+ // TODO(dfc) A note on blocking channel use.
+ // The msg, win, data and dataExt channels of a clientChan can
+ // cause this loop to block indefinately if the consumer does
+ // not service them.
+ switch packet[0] {
+ case msgChannelData:
+ if len(packet) < 9 {
+ // malformed data packet
+ break
+ }
+ peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
+ if length := int(packet[5])<<24 | int(packet[6])<<16 | int(packet[7])<<8 | int(packet[8]); length > 0 {
+ packet = packet[9:]
+ c.getChan(peersId).stdout.handleData(packet[:length])
+ }
+ case msgChannelExtendedData:
+ if len(packet) < 13 {
+ // malformed data packet
+ break
+ }
+ peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
+ datatype := uint32(packet[5])<<24 | uint32(packet[6])<<16 | uint32(packet[7])<<8 | uint32(packet[8])
+ if length := int(packet[9])<<24 | int(packet[10])<<16 | int(packet[11])<<8 | int(packet[12]); length > 0 {
+ packet = packet[13:]
+ // RFC 4254 5.2 defines data_type_code 1 to be data destined
+ // for stderr on interactive sessions. Other data types are
+ // silently discarded.
+ if datatype == 1 {
+ c.getChan(peersId).stderr.handleData(packet[:length])
+ }
+ }
+ default:
+ switch msg := decode(packet).(type) {
+ case *channelOpenMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelOpenConfirmMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelOpenFailureMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelCloseMsg:
+ ch := c.getChan(msg.PeersId)
+ ch.theyClosed = true
+ close(ch.stdin.win)
+ ch.stdout.eof()
+ ch.stderr.eof()
+ close(ch.msg)
+ if !ch.weClosed {
+ ch.weClosed = true
+ ch.sendClose()
+ }
+ c.chanlist.remove(msg.PeersId)
+ case *channelEOFMsg:
+ ch := c.getChan(msg.PeersId)
+ ch.stdout.eof()
+ // RFC 4254 is mute on how EOF affects dataExt messages but
+ // it is logical to signal EOF at the same time.
+ ch.stderr.eof()
+ case *channelRequestSuccessMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelRequestFailureMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *channelRequestMsg:
+ c.getChan(msg.PeersId).msg <- msg
+ case *windowAdjustMsg:
+ c.getChan(msg.PeersId).stdin.win <- int(msg.AdditionalBytes)
+ case *disconnectMsg:
+ break
+ default:
+ fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg)
+ }
+ }
+ }
+}
+
+// Dial connects to the given network address using net.Dial and
+// then initiates a SSH handshake, returning the resulting client connection.
+func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) {
+ conn, err := net.Dial(network, addr)
+ if err != nil {
+ return nil, err
+ }
+ return Client(conn, config)
+}
+
+// A ClientConfig structure is used to configure a ClientConn. After one has
+// been passed to an SSH function it must not be modified.
+type ClientConfig struct {
+ // Rand provides the source of entropy for key exchange. If Rand is
+ // nil, the cryptographic random reader in package crypto/rand will
+ // be used.
+ Rand io.Reader
+
+ // The username to authenticate.
+ User string
+
+ // A slice of ClientAuth methods. Only the first instance
+ // of a particular RFC 4252 method will be used during authentication.
+ Auth []ClientAuth
+
+ // Cryptographic-related configuration.
+ Crypto CryptoConfig
+}
+
+func (c *ClientConfig) rand() io.Reader {
+ if c.Rand == nil {
+ return rand.Reader
+ }
+ return c.Rand
+}
+
+// A clientChan represents a single RFC 4254 channel that is multiplexed
+// over a single SSH connection.
+type clientChan struct {
+ packetWriter
+ id, peersId uint32
+ stdin *chanWriter // receives window adjustments
+ stdout *chanReader // receives the payload of channelData messages
+ stderr *chanReader // receives the payload of channelExtendedData messages
+ msg chan interface{} // incoming messages
+ theyClosed bool // indicates the close msg has been received from the remote side
+ weClosed bool // incidates the close msg has been sent from our side
+}
+
+// newClientChan returns a partially constructed *clientChan
+// using the local id provided. To be usable clientChan.peersId
+// needs to be assigned once known.
+func newClientChan(t *transport, id uint32) *clientChan {
+ c := &clientChan{
+ packetWriter: t,
+ id: id,
+ msg: make(chan interface{}, 16),
+ }
+ c.stdin = &chanWriter{
+ win: make(chan int, 16),
+ clientChan: c,
+ }
+ c.stdout = &chanReader{
+ data: make(chan []byte, 16),
+ clientChan: c,
+ }
+ c.stderr = &chanReader{
+ data: make(chan []byte, 16),
+ clientChan: c,
+ }
+ return c
+}
+
+// waitForChannelOpenResponse, if successful, fills out
+// the peerId and records any initial window advertisement.
+func (c *clientChan) waitForChannelOpenResponse() error {
+ switch msg := (<-c.msg).(type) {
+ case *channelOpenConfirmMsg:
+ // fixup peersId field
+ c.peersId = msg.MyId
+ c.stdin.win <- int(msg.MyWindow)
+ return nil
+ case *channelOpenFailureMsg:
+ return errors.New(safeString(msg.Message))
+ }
+ return errors.New("unexpected packet")
+}
+
+// sendEOF sends EOF to the server. RFC 4254 Section 5.3
+func (c *clientChan) sendEOF() error {
+ return c.writePacket(marshal(msgChannelEOF, channelEOFMsg{
+ PeersId: c.peersId,
+ }))
+}
+
+// sendClose signals the intent to close the channel.
+func (c *clientChan) sendClose() error {
+ return c.writePacket(marshal(msgChannelClose, channelCloseMsg{
+ PeersId: c.peersId,
+ }))
+}
+
+// Close closes the channel. This does not close the underlying connection.
+func (c *clientChan) Close() error {
+ if !c.weClosed {
+ c.weClosed = true
+ return c.sendClose()
+ }
+ return nil
+}
+
+// Thread safe channel list.
+type chanlist struct {
+ // protects concurrent access to chans
+ sync.Mutex
+ // chans are indexed by the local id of the channel, clientChan.id.
+ // The PeersId value of messages received by ClientConn.mainLoop is
+ // used to locate the right local clientChan in this slice.
+ chans []*clientChan
+}
+
+// Allocate a new ClientChan with the next avail local id.
+func (c *chanlist) newChan(t *transport) *clientChan {
+ c.Lock()
+ defer c.Unlock()
+ for i := range c.chans {
+ if c.chans[i] == nil {
+ ch := newClientChan(t, uint32(i))
+ c.chans[i] = ch
+ return ch
+ }
+ }
+ i := len(c.chans)
+ ch := newClientChan(t, uint32(i))
+ c.chans = append(c.chans, ch)
+ return ch
+}
+
+func (c *chanlist) getChan(id uint32) *clientChan {
+ c.Lock()
+ defer c.Unlock()
+ return c.chans[int(id)]
+}
+
+func (c *chanlist) remove(id uint32) {
+ c.Lock()
+ defer c.Unlock()
+ c.chans[int(id)] = nil
+}
+
+// A chanWriter represents the stdin of a remote process.
+type chanWriter struct {
+ win chan int // receives window adjustments
+ rwin int // current rwin size
+ clientChan *clientChan // the channel backing this writer
+}
+
+// Write writes data to the remote process's standard input.
+func (w *chanWriter) Write(data []byte) (written int, err error) {
+ for len(data) > 0 {
+ for w.rwin < 1 {
+ win, ok := <-w.win
+ if !ok {
+ return 0, io.EOF
+ }
+ w.rwin += win
+ }
+ n := min(len(data), w.rwin)
+ peersId := w.clientChan.peersId
+ packet := []byte{
+ msgChannelData,
+ byte(peersId >> 24), byte(peersId >> 16), byte(peersId >> 8), byte(peersId),
+ byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
+ }
+ if err = w.clientChan.writePacket(append(packet, data[:n]...)); err != nil {
+ break
+ }
+ data = data[n:]
+ w.rwin -= n
+ written += n
+ }
+ return
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func (w *chanWriter) Close() error {
+ return w.clientChan.sendEOF()
+}
+
+// A chanReader represents stdout or stderr of a remote process.
+type chanReader struct {
+ // TODO(dfc) a fixed size channel may not be the right data structure.
+ // If writes to this channel block, they will block mainLoop, making
+ // it unable to receive new messages from the remote side.
+ data chan []byte // receives data from remote
+ dataClosed bool // protects data from being closed twice
+ clientChan *clientChan // the channel backing this reader
+ buf []byte
+}
+
+// eof signals to the consumer that there is no more data to be received.
+func (r *chanReader) eof() {
+ if !r.dataClosed {
+ r.dataClosed = true
+ close(r.data)
+ }
+}
+
+// handleData sends buf to the reader's consumer. If r.data is closed
+// the data will be silently discarded
+func (r *chanReader) handleData(buf []byte) {
+ if !r.dataClosed {
+ r.data <- buf
+ }
+}
+
+// Read reads data from the remote process's stdout or stderr.
+func (r *chanReader) Read(data []byte) (int, error) {
+ var ok bool
+ for {
+ if len(r.buf) > 0 {
+ n := copy(data, r.buf)
+ r.buf = r.buf[n:]
+ msg := windowAdjustMsg{
+ PeersId: r.clientChan.peersId,
+ AdditionalBytes: uint32(n),
+ }
+ return n, r.clientChan.writePacket(marshal(msgChannelWindowAdjust, msg))
+ }
+ r.buf, ok = <-r.data
+ if !ok {
+ return 0, io.EOF
+ }
+ }
+ panic("unreachable")
+}
diff --git a/ssh/client_auth.go b/ssh/client_auth.go
new file mode 100644
index 0000000..3a7e9fb
--- /dev/null
+++ b/ssh/client_auth.go
@@ -0,0 +1,316 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "errors"
+ "io"
+)
+
+// authenticate authenticates with the remote server. See RFC 4252.
+func (c *ClientConn) authenticate(session []byte) error {
+ // initiate user auth session
+ if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
+ return err
+ }
+ packet, err := c.readPacket()
+ if err != nil {
+ return err
+ }
+ var serviceAccept serviceAcceptMsg
+ if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
+ return err
+ }
+ // during the authentication phase the client first attempts the "none" method
+ // then any untried methods suggested by the server.
+ tried, remain := make(map[string]bool), make(map[string]bool)
+ for auth := ClientAuth(new(noneAuth)); auth != nil; {
+ ok, methods, err := auth.auth(session, c.config.User, c.transport, c.config.rand())
+ if err != nil {
+ return err
+ }
+ if ok {
+ // success
+ return nil
+ }
+ tried[auth.method()] = true
+ delete(remain, auth.method())
+ for _, meth := range methods {
+ if tried[meth] {
+ // if we've tried meth already, skip it.
+ continue
+ }
+ remain[meth] = true
+ }
+ auth = nil
+ for _, a := range c.config.Auth {
+ if remain[a.method()] {
+ auth = a
+ break
+ }
+ }
+ }
+ return errors.New("ssh: unable to authenticate, no supported methods remain")
+}
+
+// A ClientAuth represents an instance of an RFC 4252 authentication method.
+type ClientAuth interface {
+ // auth authenticates user over transport t.
+ // Returns true if authentication is successful.
+ // If authentication is not successful, a []string of alternative
+ // method names is returned.
+ auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error)
+
+ // method returns the RFC 4252 method name.
+ method() string
+}
+
+// "none" authentication, RFC 4252 section 5.2.
+type noneAuth int
+
+func (n *noneAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+ if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: "none",
+ })); err != nil {
+ return false, nil, err
+ }
+
+ return handleAuthResponse(t)
+}
+
+func (n *noneAuth) method() string {
+ return "none"
+}
+
+// "password" authentication, RFC 4252 Section 8.
+type passwordAuth struct {
+ ClientPassword
+}
+
+func (p *passwordAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+ type passwordAuthMsg struct {
+ User string
+ Service string
+ Method string
+ Reply bool
+ Password string
+ }
+
+ pw, err := p.Password(user)
+ if err != nil {
+ return false, nil, err
+ }
+
+ if err := t.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: "password",
+ Reply: false,
+ Password: pw,
+ })); err != nil {
+ return false, nil, err
+ }
+
+ return handleAuthResponse(t)
+}
+
+func (p *passwordAuth) method() string {
+ return "password"
+}
+
+// A ClientPassword implements access to a client's passwords.
+type ClientPassword interface {
+ // Password returns the password to use for user.
+ Password(user string) (password string, err error)
+}
+
+// ClientAuthPassword returns a ClientAuth using password authentication.
+func ClientAuthPassword(impl ClientPassword) ClientAuth {
+ return &passwordAuth{impl}
+}
+
+// ClientKeyring implements access to a client key ring.
+type ClientKeyring interface {
+ // Key returns the i'th rsa.Publickey or dsa.Publickey, or nil if
+ // no key exists at i.
+ Key(i int) (key interface{}, err error)
+
+ // Sign returns a signature of the given data using the i'th key
+ // and the supplied random source.
+ Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
+}
+
+// "publickey" authentication, RFC 4252 Section 7.
+type publickeyAuth struct {
+ ClientKeyring
+}
+
+type publickeyAuthMsg struct {
+ User string
+ Service string
+ Method string
+ // HasSig indicates to the reciver packet that the auth request is signed and
+ // should be used for authentication of the request.
+ HasSig bool
+ Algoname string
+ Pubkey string
+ // Sig is defined as []byte so marshal will exclude it during validateKey
+ Sig []byte `ssh:"rest"`
+}
+
+func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+
+ // Authentication is performed in two stages. The first stage sends an
+ // enquiry to test if each key is acceptable to the remote. The second
+ // stage attempts to authenticate with the valid keys obtained in the
+ // first stage.
+
+ var index int
+ // a map of public keys to their index in the keyring
+ validKeys := make(map[int]interface{})
+ for {
+ key, err := p.Key(index)
+ if err != nil {
+ return false, nil, err
+ }
+ if key == nil {
+ // no more keys in the keyring
+ break
+ }
+
+ if ok, err := p.validateKey(key, user, t); ok {
+ validKeys[index] = key
+ } else {
+ if err != nil {
+ return false, nil, err
+ }
+ }
+ index++
+ }
+
+ // methods that may continue if this auth is not successful.
+ var methods []string
+ for i, key := range validKeys {
+ pubkey := serializePublickey(key)
+ algoname := algoName(key)
+ sign, err := p.Sign(i, rand, buildDataSignedForAuth(session, userAuthRequestMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: p.method(),
+ }, []byte(algoname), pubkey))
+ if err != nil {
+ return false, nil, err
+ }
+ // manually wrap the serialized signature in a string
+ s := serializeSignature(algoname, sign)
+ sig := make([]byte, stringLength(s))
+ marshalString(sig, s)
+ msg := publickeyAuthMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: p.method(),
+ HasSig: true,
+ Algoname: algoname,
+ Pubkey: string(pubkey),
+ Sig: sig,
+ }
+ p := marshal(msgUserAuthRequest, msg)
+ if err := t.writePacket(p); err != nil {
+ return false, nil, err
+ }
+ success, methods, err := handleAuthResponse(t)
+ if err != nil {
+ return false, nil, err
+ }
+ if success {
+ return success, methods, err
+ }
+ }
+ return false, methods, nil
+}
+
+// validateKey validates the key provided it is acceptable to the server.
+func (p *publickeyAuth) validateKey(key interface{}, user string, t *transport) (bool, error) {
+ pubkey := serializePublickey(key)
+ algoname := algoName(key)
+ msg := publickeyAuthMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: p.method(),
+ HasSig: false,
+ Algoname: algoname,
+ Pubkey: string(pubkey),
+ }
+ if err := t.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
+ return false, err
+ }
+
+ return p.confirmKeyAck(key, t)
+}
+
+func (p *publickeyAuth) confirmKeyAck(key interface{}, t *transport) (bool, error) {
+ pubkey := serializePublickey(key)
+ algoname := algoName(key)
+
+ for {
+ packet, err := t.readPacket()
+ if err != nil {
+ return false, err
+ }
+ switch packet[0] {
+ case msgUserAuthBanner:
+ // TODO(gpaul): add callback to present the banner to the user
+ case msgUserAuthPubKeyOk:
+ msg := decode(packet).(*userAuthPubKeyOkMsg)
+ if msg.Algo != algoname || msg.PubKey != string(pubkey) {
+ return false, nil
+ }
+ return true, nil
+ case msgUserAuthFailure:
+ return false, nil
+ default:
+ return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
+ }
+ }
+ panic("unreachable")
+}
+
+func (p *publickeyAuth) method() string {
+ return "publickey"
+}
+
+// ClientAuthKeyring returns a ClientAuth using public key authentication.
+func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
+ return &publickeyAuth{impl}
+}
+
+// handleAuthResponse returns whether the preceding authentication request succeeded
+// along with a list of remaining authentication methods to try next and
+// an error if an unexpected response was received.
+func handleAuthResponse(t *transport) (bool, []string, error) {
+ for {
+ packet, err := t.readPacket()
+ if err != nil {
+ return false, nil, err
+ }
+
+ switch packet[0] {
+ case msgUserAuthBanner:
+ // TODO: add callback to present the banner to the user
+ case msgUserAuthFailure:
+ msg := decode(packet).(*userAuthFailureMsg)
+ return false, msg.Methods, nil
+ case msgUserAuthSuccess:
+ return true, nil, nil
+ case msgDisconnect:
+ return false, nil, io.EOF
+ default:
+ return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
+ }
+ }
+ panic("unreachable")
+}
diff --git a/ssh/client_auth_test.go b/ssh/client_auth_test.go
new file mode 100644
index 0000000..c41a93b
--- /dev/null
+++ b/ssh/client_auth_test.go
@@ -0,0 +1,257 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/dsa"
+ "crypto/rsa"
+ _ "crypto/sha1"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+ "io"
+ "io/ioutil"
+ "math/big"
+ "testing"
+)
+
+// private key for mock server
+const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
+70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
+9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
+tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
+s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
+qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
++IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
+riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
+D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
+atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
+b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
+ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
+MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
+KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
+e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
+D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
+3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
+orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
+64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
+XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
+QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
+/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
+I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
+gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
+NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
+-----END RSA PRIVATE KEY-----`
+
+const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld
+r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ
+tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC
+nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW
+2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB
+y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr
+rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg==
+-----END RSA PRIVATE KEY-----`
+
+// keychain implements the ClientPublickey interface
+type keychain struct {
+ keys []interface{}
+}
+
+func (k *keychain) Key(i int) (interface{}, error) {
+ if i < 0 || i >= len(k.keys) {
+ return nil, nil
+ }
+ switch key := k.keys[i].(type) {
+ case *rsa.PrivateKey:
+ return key.PublicKey, nil
+ case *dsa.PrivateKey:
+ return key.PublicKey, nil
+ }
+ panic("unknown key type")
+}
+
+func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
+ hashFunc := crypto.SHA1
+ h := hashFunc.New()
+ h.Write(data)
+ digest := h.Sum(nil)
+ switch key := k.keys[i].(type) {
+ case *rsa.PrivateKey:
+ return rsa.SignPKCS1v15(rand, key, hashFunc, digest)
+ }
+ return nil, errors.New("unknown key type")
+}
+
+func (k *keychain) loadPEM(file string) error {
+ buf, err := ioutil.ReadFile(file)
+ if err != nil {
+ return err
+ }
+ block, _ := pem.Decode(buf)
+ if block == nil {
+ return errors.New("ssh: no key found")
+ }
+ r, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+ k.keys = append(k.keys, r)
+ return nil
+}
+
+// password implements the ClientPassword interface
+type password string
+
+func (p password) Password(user string) (string, error) {
+ return string(p), nil
+}
+
+// reused internally by tests
+var (
+ rsakey *rsa.PrivateKey
+ dsakey *dsa.PrivateKey
+ clientKeychain = new(keychain)
+ clientPassword = password("tiger")
+ serverConfig = &ServerConfig{
+ PasswordCallback: func(user, pass string) bool {
+ return user == "testuser" && pass == string(clientPassword)
+ },
+ PublicKeyCallback: func(user, algo string, pubkey []byte) bool {
+ key := clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey
+ expected := []byte(serializePublickey(key))
+ algoname := algoName(key)
+ return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
+ },
+ }
+)
+
+func init() {
+ if err := serverConfig.SetRSAPrivateKey([]byte(testServerPrivateKey)); err != nil {
+ panic("unable to set private key: " + err.Error())
+ }
+
+ block, _ := pem.Decode([]byte(testClientPrivateKey))
+ rsakey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
+
+ clientKeychain.keys = append(clientKeychain.keys, rsakey)
+ dsakey = new(dsa.PrivateKey)
+ // taken from crypto/dsa/dsa_test.go
+ dsakey.P, _ = new(big.Int).SetString("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF", 16)
+ dsakey.Q, _ = new(big.Int).SetString("E1D3391245933D68A0714ED34BBCB7A1F422B9C1", 16)
+ dsakey.G, _ = new(big.Int).SetString("634364FC25248933D01D1993ECABD0657CC0CB2CEED7ED2E3E8AECDFCDC4A25C3B15E9E3B163ACA2984B5539181F3EFF1A5E8903D71D5B95DA4F27202B77D2C44B430BB53741A8D59A8F86887525C9F2A6A5980A195EAA7F2FF910064301DEF89D3AA213E1FAC7768D89365318E370AF54A112EFBA9246D9158386BA1B4EEFDA", 16)
+ dsakey.Y, _ = new(big.Int).SetString("32969E5780CFE1C849A1C276D7AEB4F38A23B591739AA2FE197349AEEBD31366AEE5EB7E6C6DDB7C57D02432B30DB5AA66D9884299FAA72568944E4EEDC92EA3FBC6F39F53412FBCC563208F7C15B737AC8910DBC2D9C9B8C001E72FDC40EB694AB1F06A5A2DBD18D9E36C66F31F566742F11EC0A52E9F7B89355C02FB5D32D2", 16)
+ dsakey.X, _ = new(big.Int).SetString("5078D4D29795CBE76D3AACFE48C9AF0BCDBEE91A", 16)
+}
+
+// newMockAuthServer creates a new Server bound to
+// the loopback interface. The server exits after
+// processing one handshake.
+func newMockAuthServer(t *testing.T) string {
+ l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
+ if err != nil {
+ t.Fatalf("unable to newMockAuthServer: %s", err)
+ }
+ go func() {
+ defer l.Close()
+ c, err := l.Accept()
+ defer c.Close()
+ if err != nil {
+ t.Errorf("Unable to accept incoming connection: %v", err)
+ return
+ }
+ if err := c.Handshake(); err != nil {
+ // not Errorf because this is expected to
+ // fail for some tests.
+ t.Logf("Handshaking error: %v", err)
+ return
+ }
+ }()
+ return l.Addr().String()
+}
+
+func TestClientAuthPublickey(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthKeyring(clientKeychain),
+ },
+ }
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ c.Close()
+}
+
+func TestClientAuthPassword(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthPassword(clientPassword),
+ },
+ }
+
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ c.Close()
+}
+
+func TestClientAuthWrongPassword(t *testing.T) {
+ wrongPw := password("wrong")
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthPassword(wrongPw),
+ ClientAuthKeyring(clientKeychain),
+ },
+ }
+
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ c.Close()
+}
+
+// the mock server will only authenticate ssh-rsa keys
+func TestClientAuthInvalidPublickey(t *testing.T) {
+ kc := new(keychain)
+ kc.keys = append(kc.keys, dsakey)
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthKeyring(kc),
+ },
+ }
+
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err == nil {
+ c.Close()
+ t.Fatalf("dsa private key should not have authenticated with rsa public key")
+ }
+}
+
+// the client should authenticate with the second key
+func TestClientAuthRSAandDSA(t *testing.T) {
+ kc := new(keychain)
+ kc.keys = append(kc.keys, dsakey, rsakey)
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthKeyring(kc),
+ },
+ }
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err != nil {
+ t.Fatalf("client could not authenticate with rsa key: %v", err)
+ }
+ c.Close()
+}
diff --git a/ssh/client_func_test.go b/ssh/client_func_test.go
new file mode 100644
index 0000000..b4bdba9
--- /dev/null
+++ b/ssh/client_func_test.go
@@ -0,0 +1,61 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// ClientConn functional tests.
+// These tests require a running ssh server listening on port 22
+// on the local host. Functional tests will be skipped unless
+// -ssh.user and -ssh.pass must be passed to gotest.
+
+import (
+ "flag"
+ "testing"
+)
+
+var (
+ sshuser = flag.String("ssh.user", "", "ssh username")
+ sshpass = flag.String("ssh.pass", "", "ssh password")
+ sshprivkey = flag.String("ssh.privkey", "", "ssh privkey file")
+)
+
+func TestFuncPasswordAuth(t *testing.T) {
+ if *sshuser == "" {
+ t.Log("ssh.user not defined, skipping test")
+ return
+ }
+ config := &ClientConfig{
+ User: *sshuser,
+ Auth: []ClientAuth{
+ ClientAuthPassword(password(*sshpass)),
+ },
+ }
+ conn, err := Dial("tcp", "localhost:22", config)
+ if err != nil {
+ t.Fatalf("Unable to connect: %s", err)
+ }
+ defer conn.Close()
+}
+
+func TestFuncPublickeyAuth(t *testing.T) {
+ if *sshuser == "" {
+ t.Log("ssh.user not defined, skipping test")
+ return
+ }
+ kc := new(keychain)
+ if err := kc.loadPEM(*sshprivkey); err != nil {
+ t.Fatalf("unable to load private key: %s", err)
+ }
+ config := &ClientConfig{
+ User: *sshuser,
+ Auth: []ClientAuth{
+ ClientAuthKeyring(kc),
+ },
+ }
+ conn, err := Dial("tcp", "localhost:22", config)
+ if err != nil {
+ t.Fatalf("unable to connect: %s", err)
+ }
+ defer conn.Close()
+}
diff --git a/ssh/common.go b/ssh/common.go
new file mode 100644
index 0000000..6844fb8
--- /dev/null
+++ b/ssh/common.go
@@ -0,0 +1,239 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "crypto/dsa"
+ "crypto/rsa"
+ "math/big"
+ "strconv"
+ "sync"
+)
+
+// These are string constants in the SSH protocol.
+const (
+ kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
+ hostAlgoRSA = "ssh-rsa"
+ macSHA196 = "hmac-sha1-96"
+ compressionNone = "none"
+ serviceUserAuth = "ssh-userauth"
+ serviceSSH = "ssh-connection"
+)
+
+var supportedKexAlgos = []string{kexAlgoDH14SHA1}
+var supportedHostKeyAlgos = []string{hostAlgoRSA}
+var supportedMACs = []string{macSHA196}
+var supportedCompressions = []string{compressionNone}
+
+// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
+type dhGroup struct {
+ g, p *big.Int
+}
+
+// dhGroup14 is the group called diffie-hellman-group14-sha1 in RFC 4253 and
+// Oakley Group 14 in RFC 3526.
+var dhGroup14 *dhGroup
+
+var dhGroup14Once sync.Once
+
+func initDHGroup14() {
+ p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
+
+ dhGroup14 = &dhGroup{
+ g: new(big.Int).SetInt64(2),
+ p: p,
+ }
+}
+
+// UnexpectedMessageError results when the SSH message that we received didn't
+// match what we wanted.
+type UnexpectedMessageError struct {
+ expected, got uint8
+}
+
+func (u UnexpectedMessageError) Error() string {
+ return "ssh: unexpected message type " + strconv.Itoa(int(u.got)) + " (expected " + strconv.Itoa(int(u.expected)) + ")"
+}
+
+// ParseError results from a malformed SSH message.
+type ParseError struct {
+ msgType uint8
+}
+
+func (p ParseError) Error() string {
+ return "ssh: parse error in message type " + strconv.Itoa(int(p.msgType))
+}
+
+type handshakeMagics struct {
+ clientVersion, serverVersion []byte
+ clientKexInit, serverKexInit []byte
+}
+
+func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
+ for _, clientAlgo := range clientAlgos {
+ for _, serverAlgo := range serverAlgos {
+ if clientAlgo == serverAlgo {
+ return clientAlgo, true
+ }
+ }
+ }
+
+ return
+}
+
+func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *kexInitMsg) (kexAlgo, hostKeyAlgo string, ok bool) {
+ kexAlgo, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos)
+ if !ok {
+ return
+ }
+
+ hostKeyAlgo, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
+ if !ok {
+ return
+ }
+
+ transport.writer.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
+ if !ok {
+ return
+ }
+
+ transport.reader.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
+ if !ok {
+ return
+ }
+
+ transport.writer.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
+ if !ok {
+ return
+ }
+
+ transport.reader.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
+ if !ok {
+ return
+ }
+
+ transport.writer.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
+ if !ok {
+ return
+ }
+
+ transport.reader.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
+ if !ok {
+ return
+ }
+
+ ok = true
+ return
+}
+
+// Cryptographic configuration common to both ServerConfig and ClientConfig.
+type CryptoConfig struct {
+ // The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
+ // used.
+ Ciphers []string
+}
+
+func (c *CryptoConfig) ciphers() []string {
+ if c.Ciphers == nil {
+ return DefaultCipherOrder
+ }
+ return c.Ciphers
+}
+
+// serialize a signed slice according to RFC 4254 6.6.
+func serializeSignature(algoname string, sig []byte) []byte {
+ length := stringLength([]byte(algoname))
+ length += stringLength(sig)
+
+ ret := make([]byte, length)
+ r := marshalString(ret, []byte(algoname))
+ r = marshalString(r, sig)
+
+ return ret
+}
+
+// serialize an rsa.PublicKey or dsa.PublicKey according to RFC 4253 6.6.
+func serializePublickey(key interface{}) []byte {
+ algoname := algoName(key)
+ switch key := key.(type) {
+ case rsa.PublicKey:
+ e := new(big.Int).SetInt64(int64(key.E))
+ length := stringLength([]byte(algoname))
+ length += intLength(e)
+ length += intLength(key.N)
+ ret := make([]byte, length)
+ r := marshalString(ret, []byte(algoname))
+ r = marshalInt(r, e)
+ marshalInt(r, key.N)
+ return ret
+ case dsa.PublicKey:
+ length := stringLength([]byte(algoname))
+ length += intLength(key.P)
+ length += intLength(key.Q)
+ length += intLength(key.G)
+ length += intLength(key.Y)
+ ret := make([]byte, length)
+ r := marshalString(ret, []byte(algoname))
+ r = marshalInt(r, key.P)
+ r = marshalInt(r, key.Q)
+ r = marshalInt(r, key.G)
+ marshalInt(r, key.Y)
+ return ret
+ }
+ panic("unexpected key type")
+}
+
+func algoName(key interface{}) string {
+ switch key.(type) {
+ case rsa.PublicKey:
+ return "ssh-rsa"
+ case dsa.PublicKey:
+ return "ssh-dss"
+ }
+ panic("unexpected key type")
+}
+
+// buildDataSignedForAuth returns the data that is signed in order to prove
+// posession of a private key. See RFC 4252, section 7.
+func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
+ user := []byte(req.User)
+ service := []byte(req.Service)
+ method := []byte(req.Method)
+
+ length := stringLength(sessionId)
+ length += 1
+ length += stringLength(user)
+ length += stringLength(service)
+ length += stringLength(method)
+ length += 1
+ length += stringLength(algo)
+ length += stringLength(pubKey)
+
+ ret := make([]byte, length)
+ r := marshalString(ret, sessionId)
+ r[0] = msgUserAuthRequest
+ r = r[1:]
+ r = marshalString(r, user)
+ r = marshalString(r, service)
+ r = marshalString(r, method)
+ r[0] = 1
+ r = r[1:]
+ r = marshalString(r, algo)
+ r = marshalString(r, pubKey)
+ return ret
+}
+
+// safeString sanitises s according to RFC 4251, section 9.2.
+// All control characters except tab, carriage return and newline are
+// replaced by 0x20.
+func safeString(s string) string {
+ out := []byte(s)
+ for i, c := range out {
+ if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
+ out[i] = 0x20
+ }
+ }
+ return string(out)
+}
diff --git a/ssh/common_test.go b/ssh/common_test.go
new file mode 100644
index 0000000..058fb04
--- /dev/null
+++ b/ssh/common_test.go
@@ -0,0 +1,26 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "testing"
+)
+
+func TestSafeString(t *testing.T) {
+ strings := map[string]string{
+ "\x20\x0d\x0a": "\x20\x0d\x0a",
+ "flibble": "flibble",
+ "new\x20line": "new\x20line",
+ "123456\x07789": "123456 789",
+ "\t\t\x10\r\n": "\t\t \r\n",
+ }
+
+ for s, expected := range strings {
+ actual := safeString(s)
+ if expected != actual {
+ t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual))
+ }
+ }
+}
diff --git a/ssh/doc.go b/ssh/doc.go
new file mode 100644
index 0000000..e7deb5e
--- /dev/null
+++ b/ssh/doc.go
@@ -0,0 +1,127 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package ssh implements an SSH client and server.
+
+SSH is a transport security protocol, an authentication protocol and a
+family of application protocols. The most typical application level
+protocol is a remote shell and this is specifically implemented. However,
+the multiplexed nature of SSH is exposed to users that wish to support
+others.
+
+An SSH server is represented by a ServerConfig, which holds certificate
+details and handles authentication of ServerConns.
+
+ config := new(ssh.ServerConfig)
+ config.PubKeyCallback = pubKeyAuth
+ config.PasswordCallback = passwordAuth
+
+ pemBytes, err := ioutil.ReadFile("id_rsa")
+ if err != nil {
+ panic("Failed to load private key")
+ }
+ err = config.SetRSAPrivateKey(pemBytes)
+ if err != nil {
+ panic("Failed to parse private key")
+ }
+
+Once a ServerConfig has been configured, connections can be accepted.
+
+ listener := Listen("tcp", "0.0.0.0:2022", config)
+ sConn, err := listener.Accept()
+ if err != nil {
+ panic("failed to accept incoming connection")
+ }
+ if err := sConn.Handshake(conn); err != nil {
+ panic("failed to handshake")
+ }
+
+An SSH connection multiplexes several channels, which must be accepted themselves:
+
+ for {
+ channel, err := sConn.Accept()
+ if err != nil {
+ panic("error from Accept")
+ }
+
+ ...
+ }
+
+Accept reads from the connection, demultiplexes packets to their corresponding
+channels and returns when a new channel request is seen. Some goroutine must
+always be calling Accept; otherwise no messages will be forwarded to the
+channels.
+
+Channels have a type, depending on the application level protocol intended. In
+the case of a shell, the type is "session" and ServerShell may be used to
+present a simple terminal interface.
+
+ if channel.ChannelType() != "session" {
+ channel.Reject(UnknownChannelType, "unknown channel type")
+ return
+ }
+ channel.Accept()
+
+ term := terminal.NewTerminal(channel, "> ")
+ serverTerm := &ssh.ServerTerminal{
+ Term: term,
+ Channel: channel,
+ }
+ go func() {
+ defer channel.Close()
+ for {
+ line, err := serverTerm.ReadLine()
+ if err != nil {
+ break
+ }
+ println(line)
+ }
+ return
+ }()
+
+To authenticate with the remote server you must pass at least one implementation of
+ClientAuth via the Auth field in ClientConfig.
+
+ // password implements the ClientPassword interface
+ type password string
+
+ func (p password) Password(user string) (string, error) {
+ return string(p), nil
+ }
+
+ config := &ssh.ClientConfig {
+ User: "username",
+ Auth: []ClientAuth {
+ // ClientAuthPassword wraps a ClientPassword implementation
+ // in a type that implements ClientAuth.
+ ClientAuthPassword(password("yourpassword")),
+ }
+ }
+
+An SSH client is represented with a ClientConn. Currently only the "password"
+authentication method is supported.
+
+ config := &ClientConfig{
+ User: "username",
+ Auth: []ClientAuth{ ... },
+ }
+ client, err := Dial("yourserver.com:22", config)
+
+Each ClientConn can support multiple interactive sessions, represented by a Session.
+
+ session, err := client.NewSession()
+
+Once a Session is created, you can execute a single command on the remote side
+using the Exec method.
+
+ b := bytes.NewBuffer()
+ session.Stdin = b
+ if err := session.Run("/usr/bin/whoami"); err != nil {
+ panic("Failed to exec: " + err.String())
+ }
+ fmt.Println(bytes.String())
+ session.Close()
+*/
+package ssh
diff --git a/ssh/messages.go b/ssh/messages.go
new file mode 100644
index 0000000..34ad131
--- /dev/null
+++ b/ssh/messages.go
@@ -0,0 +1,640 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "bytes"
+ "io"
+ "math/big"
+ "reflect"
+)
+
+// These are SSH message type numbers. They are scattered around several
+// documents but many were taken from
+// http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
+const (
+ msgDisconnect = 1
+ msgIgnore = 2
+ msgUnimplemented = 3
+ msgDebug = 4
+ msgServiceRequest = 5
+ msgServiceAccept = 6
+
+ msgKexInit = 20
+ msgNewKeys = 21
+
+ msgKexDHInit = 30
+ msgKexDHReply = 31
+
+ msgUserAuthRequest = 50
+ msgUserAuthFailure = 51
+ msgUserAuthSuccess = 52
+ msgUserAuthBanner = 53
+ msgUserAuthPubKeyOk = 60
+
+ msgGlobalRequest = 80
+ msgRequestSuccess = 81
+ msgRequestFailure = 82
+
+ msgChannelOpen = 90
+ msgChannelOpenConfirm = 91
+ msgChannelOpenFailure = 92
+ msgChannelWindowAdjust = 93
+ msgChannelData = 94
+ msgChannelExtendedData = 95
+ msgChannelEOF = 96
+ msgChannelClose = 97
+ msgChannelRequest = 98
+ msgChannelSuccess = 99
+ msgChannelFailure = 100
+)
+
+// SSH messages:
+//
+// These structures mirror the wire format of the corresponding SSH messages.
+// They are marshaled using reflection with the marshal and unmarshal functions
+// in this file. The only wrinkle is that a final member of type []byte with a
+// ssh tag of "rest" receives the remainder of a packet when unmarshaling.
+
+// See RFC 4253, section 11.1.
+type disconnectMsg struct {
+ Reason uint32
+ Message string
+ Language string
+}
+
+// See RFC 4253, section 7.1.
+type kexInitMsg struct {
+ Cookie [16]byte
+ KexAlgos []string
+ ServerHostKeyAlgos []string
+ CiphersClientServer []string
+ CiphersServerClient []string
+ MACsClientServer []string
+ MACsServerClient []string
+ CompressionClientServer []string
+ CompressionServerClient []string
+ LanguagesClientServer []string
+ LanguagesServerClient []string
+ FirstKexFollows bool
+ Reserved uint32
+}
+
+// See RFC 4253, section 8.
+type kexDHInitMsg struct {
+ X *big.Int
+}
+
+type kexDHReplyMsg struct {
+ HostKey []byte
+ Y *big.Int
+ Signature []byte
+}
+
+// See RFC 4253, section 10.
+type serviceRequestMsg struct {
+ Service string
+}
+
+// See RFC 4253, section 10.
+type serviceAcceptMsg struct {
+ Service string
+}
+
+// See RFC 4252, section 5.
+type userAuthRequestMsg struct {
+ User string
+ Service string
+ Method string
+ Payload []byte `ssh:"rest"`
+}
+
+// See RFC 4252, section 5.1
+type userAuthFailureMsg struct {
+ Methods []string
+ PartialSuccess bool
+}
+
+// See RFC 4254, section 5.1.
+type channelOpenMsg struct {
+ ChanType string
+ PeersId uint32
+ PeersWindow uint32
+ MaxPacketSize uint32
+ TypeSpecificData []byte `ssh:"rest"`
+}
+
+// See RFC 4254, section 5.1.
+type channelOpenConfirmMsg struct {
+ PeersId uint32
+ MyId uint32
+ MyWindow uint32
+ MaxPacketSize uint32
+ TypeSpecificData []byte `ssh:"rest"`
+}
+
+// See RFC 4254, section 5.1.
+type channelOpenFailureMsg struct {
+ PeersId uint32
+ Reason uint32
+ Message string
+ Language string
+}
+
+type channelRequestMsg struct {
+ PeersId uint32
+ Request string
+ WantReply bool
+ RequestSpecificData []byte `ssh:"rest"`
+}
+
+// See RFC 4254, section 5.4.
+type channelRequestSuccessMsg struct {
+ PeersId uint32
+}
+
+// See RFC 4254, section 5.4.
+type channelRequestFailureMsg struct {
+ PeersId uint32
+}
+
+// See RFC 4254, section 5.3
+type channelCloseMsg struct {
+ PeersId uint32
+}
+
+// See RFC 4254, section 5.3
+type channelEOFMsg struct {
+ PeersId uint32
+}
+
+// See RFC 4254, section 4
+type globalRequestMsg struct {
+ Type string
+ WantReply bool
+}
+
+// See RFC 4254, section 5.2
+type windowAdjustMsg struct {
+ PeersId uint32
+ AdditionalBytes uint32
+}
+
+// See RFC 4252, section 7
+type userAuthPubKeyOkMsg struct {
+ Algo string
+ PubKey string
+}
+
+// unmarshal parses the SSH wire data in packet into out using reflection.
+// expectedType is the expected SSH message type. It either returns nil on
+// success, or a ParseError or UnexpectedMessageError on error.
+func unmarshal(out interface{}, packet []byte, expectedType uint8) error {
+ if len(packet) == 0 {
+ return ParseError{expectedType}
+ }
+ if packet[0] != expectedType {
+ return UnexpectedMessageError{expectedType, packet[0]}
+ }
+ packet = packet[1:]
+
+ v := reflect.ValueOf(out).Elem()
+ structType := v.Type()
+ var ok bool
+ for i := 0; i < v.NumField(); i++ {
+ field := v.Field(i)
+ t := field.Type()
+ switch t.Kind() {
+ case reflect.Bool:
+ if len(packet) < 1 {
+ return ParseError{expectedType}
+ }
+ field.SetBool(packet[0] != 0)
+ packet = packet[1:]
+ case reflect.Array:
+ if t.Elem().Kind() != reflect.Uint8 {
+ panic("array of non-uint8")
+ }
+ if len(packet) < t.Len() {
+ return ParseError{expectedType}
+ }
+ for j := 0; j < t.Len(); j++ {
+ field.Index(j).Set(reflect.ValueOf(packet[j]))
+ }
+ packet = packet[t.Len():]
+ case reflect.Uint32:
+ var u32 uint32
+ if u32, packet, ok = parseUint32(packet); !ok {
+ return ParseError{expectedType}
+ }
+ field.SetUint(uint64(u32))
+ case reflect.String:
+ var s []byte
+ if s, packet, ok = parseString(packet); !ok {
+ return ParseError{expectedType}
+ }
+ field.SetString(string(s))
+ case reflect.Slice:
+ switch t.Elem().Kind() {
+ case reflect.Uint8:
+ if structType.Field(i).Tag.Get("ssh") == "rest" {
+ field.Set(reflect.ValueOf(packet))
+ packet = nil
+ } else {
+ var s []byte
+ if s, packet, ok = parseString(packet); !ok {
+ return ParseError{expectedType}
+ }
+ field.Set(reflect.ValueOf(s))
+ }
+ case reflect.String:
+ var nl []string
+ if nl, packet, ok = parseNameList(packet); !ok {
+ return ParseError{expectedType}
+ }
+ field.Set(reflect.ValueOf(nl))
+ default:
+ panic("slice of unknown type")
+ }
+ case reflect.Ptr:
+ if t == bigIntType {
+ var n *big.Int
+ if n, packet, ok = parseInt(packet); !ok {
+ return ParseError{expectedType}
+ }
+ field.Set(reflect.ValueOf(n))
+ } else {
+ panic("pointer to unknown type")
+ }
+ default:
+ panic("unknown type")
+ }
+ }
+
+ if len(packet) != 0 {
+ return ParseError{expectedType}
+ }
+
+ return nil
+}
+
+// marshal serializes the message in msg, using the given message type.
+func marshal(msgType uint8, msg interface{}) []byte {
+ var out []byte
+ out = append(out, msgType)
+
+ v := reflect.ValueOf(msg)
+ structType := v.Type()
+ for i := 0; i < v.NumField(); i++ {
+ field := v.Field(i)
+ t := field.Type()
+ switch t.Kind() {
+ case reflect.Bool:
+ var v uint8
+ if field.Bool() {
+ v = 1
+ }
+ out = append(out, v)
+ case reflect.Array:
+ if t.Elem().Kind() != reflect.Uint8 {
+ panic("array of non-uint8")
+ }
+ for j := 0; j < t.Len(); j++ {
+ out = append(out, byte(field.Index(j).Uint()))
+ }
+ case reflect.Uint32:
+ u32 := uint32(field.Uint())
+ out = append(out, byte(u32>>24))
+ out = append(out, byte(u32>>16))
+ out = append(out, byte(u32>>8))
+ out = append(out, byte(u32))
+ case reflect.String:
+ s := field.String()
+ out = append(out, byte(len(s)>>24))
+ out = append(out, byte(len(s)>>16))
+ out = append(out, byte(len(s)>>8))
+ out = append(out, byte(len(s)))
+ out = append(out, s...)
+ case reflect.Slice:
+ switch t.Elem().Kind() {
+ case reflect.Uint8:
+ length := field.Len()
+ if structType.Field(i).Tag.Get("ssh") != "rest" {
+ out = append(out, byte(length>>24))
+ out = append(out, byte(length>>16))
+ out = append(out, byte(length>>8))
+ out = append(out, byte(length))
+ }
+ for j := 0; j < length; j++ {
+ out = append(out, byte(field.Index(j).Uint()))
+ }
+ case reflect.String:
+ var length int
+ for j := 0; j < field.Len(); j++ {
+ if j != 0 {
+ length++ /* comma */
+ }
+ length += len(field.Index(j).String())
+ }
+
+ out = append(out, byte(length>>24))
+ out = append(out, byte(length>>16))
+ out = append(out, byte(length>>8))
+ out = append(out, byte(length))
+ for j := 0; j < field.Len(); j++ {
+ if j != 0 {
+ out = append(out, ',')
+ }
+ out = append(out, field.Index(j).String()...)
+ }
+ default:
+ panic("slice of unknown type")
+ }
+ case reflect.Ptr:
+ if t == bigIntType {
+ var n *big.Int
+ nValue := reflect.ValueOf(&n)
+ nValue.Elem().Set(field)
+ needed := intLength(n)
+ oldLength := len(out)
+
+ if cap(out)-len(out) < needed {
+ newOut := make([]byte, len(out), 2*(len(out)+needed))
+ copy(newOut, out)
+ out = newOut
+ }
+ out = out[:oldLength+needed]
+ marshalInt(out[oldLength:], n)
+ } else {
+ panic("pointer to unknown type")
+ }
+ }
+ }
+
+ return out
+}
+
+var bigOne = big.NewInt(1)
+
+func parseString(in []byte) (out, rest []byte, ok bool) {
+ if len(in) < 4 {
+ return
+ }
+ length := uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
+ if uint32(len(in)) < 4+length {
+ return
+ }
+ out = in[4 : 4+length]
+ rest = in[4+length:]
+ ok = true
+ return
+}
+
+var (
+ comma = []byte{','}
+ emptyNameList = []string{}
+)
+
+func parseNameList(in []byte) (out []string, rest []byte, ok bool) {
+ contents, rest, ok := parseString(in)
+ if !ok {
+ return
+ }
+ if len(contents) == 0 {
+ out = emptyNameList
+ return
+ }
+ parts := bytes.Split(contents, comma)
+ out = make([]string, len(parts))
+ for i, part := range parts {
+ out[i] = string(part)
+ }
+ return
+}
+
+func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) {
+ contents, rest, ok := parseString(in)
+ if !ok {
+ return
+ }
+ out = new(big.Int)
+
+ if len(contents) > 0 && contents[0]&0x80 == 0x80 {
+ // This is a negative number
+ notBytes := make([]byte, len(contents))
+ for i := range notBytes {
+ notBytes[i] = ^contents[i]
+ }
+ out.SetBytes(notBytes)
+ out.Add(out, bigOne)
+ out.Neg(out)
+ } else {
+ // Positive number
+ out.SetBytes(contents)
+ }
+ ok = true
+ return
+}
+
+func parseUint32(in []byte) (out uint32, rest []byte, ok bool) {
+ if len(in) < 4 {
+ return
+ }
+ out = uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
+ rest = in[4:]
+ ok = true
+ return
+}
+
+func nameListLength(namelist []string) int {
+ length := 4 /* uint32 length prefix */
+ for i, name := range namelist {
+ if i != 0 {
+ length++ /* comma */
+ }
+ length += len(name)
+ }
+ return length
+}
+
+func intLength(n *big.Int) int {
+ length := 4 /* length bytes */
+ if n.Sign() < 0 {
+ nMinus1 := new(big.Int).Neg(n)
+ nMinus1.Sub(nMinus1, bigOne)
+ bitLen := nMinus1.BitLen()
+ if bitLen%8 == 0 {
+ // The number will need 0xff padding
+ length++
+ }
+ length += (bitLen + 7) / 8
+ } else if n.Sign() == 0 {
+ // A zero is the zero length string
+ } else {
+ bitLen := n.BitLen()
+ if bitLen%8 == 0 {
+ // The number will need 0x00 padding
+ length++
+ }
+ length += (bitLen + 7) / 8
+ }
+
+ return length
+}
+
+func marshalUint32(to []byte, n uint32) []byte {
+ to[0] = byte(n >> 24)
+ to[1] = byte(n >> 16)
+ to[2] = byte(n >> 8)
+ to[3] = byte(n)
+ return to[4:]
+}
+
+func marshalUint64(to []byte, n uint64) []byte {
+ to[0] = byte(n >> 56)
+ to[1] = byte(n >> 48)
+ to[2] = byte(n >> 40)
+ to[3] = byte(n >> 32)
+ to[4] = byte(n >> 24)
+ to[5] = byte(n >> 16)
+ to[6] = byte(n >> 8)
+ to[7] = byte(n)
+ return to[8:]
+}
+
+func marshalInt(to []byte, n *big.Int) []byte {
+ lengthBytes := to
+ to = to[4:]
+ length := 0
+
+ if n.Sign() < 0 {
+ // A negative number has to be converted to two's-complement
+ // form. So we'll subtract 1 and invert. If the
+ // most-significant-bit isn't set then we'll need to pad the
+ // beginning with 0xff in order to keep the number negative.
+ nMinus1 := new(big.Int).Neg(n)
+ nMinus1.Sub(nMinus1, bigOne)
+ bytes := nMinus1.Bytes()
+ for i := range bytes {
+ bytes[i] ^= 0xff
+ }
+ if len(bytes) == 0 || bytes[0]&0x80 == 0 {
+ to[0] = 0xff
+ to = to[1:]
+ length++
+ }
+ nBytes := copy(to, bytes)
+ to = to[nBytes:]
+ length += nBytes
+ } else if n.Sign() == 0 {
+ // A zero is the zero length string
+ } else {
+ bytes := n.Bytes()
+ if len(bytes) > 0 && bytes[0]&0x80 != 0 {
+ // We'll have to pad this with a 0x00 in order to
+ // stop it looking like a negative number.
+ to[0] = 0
+ to = to[1:]
+ length++
+ }
+ nBytes := copy(to, bytes)
+ to = to[nBytes:]
+ length += nBytes
+ }
+
+ lengthBytes[0] = byte(length >> 24)
+ lengthBytes[1] = byte(length >> 16)
+ lengthBytes[2] = byte(length >> 8)
+ lengthBytes[3] = byte(length)
+ return to
+}
+
+func writeInt(w io.Writer, n *big.Int) {
+ length := intLength(n)
+ buf := make([]byte, length)
+ marshalInt(buf, n)
+ w.Write(buf)
+}
+
+func writeString(w io.Writer, s []byte) {
+ var lengthBytes [4]byte
+ lengthBytes[0] = byte(len(s) >> 24)
+ lengthBytes[1] = byte(len(s) >> 16)
+ lengthBytes[2] = byte(len(s) >> 8)
+ lengthBytes[3] = byte(len(s))
+ w.Write(lengthBytes[:])
+ w.Write(s)
+}
+
+func stringLength(s []byte) int {
+ return 4 + len(s)
+}
+
+func marshalString(to []byte, s []byte) []byte {
+ to[0] = byte(len(s) >> 24)
+ to[1] = byte(len(s) >> 16)
+ to[2] = byte(len(s) >> 8)
+ to[3] = byte(len(s))
+ to = to[4:]
+ copy(to, s)
+ return to[len(s):]
+}
+
+var bigIntType = reflect.TypeOf((*big.Int)(nil))
+
+// Decode a packet into it's corresponding message.
+func decode(packet []byte) interface{} {
+ var msg interface{}
+ switch packet[0] {
+ case msgDisconnect:
+ msg = new(disconnectMsg)
+ case msgServiceRequest:
+ msg = new(serviceRequestMsg)
+ case msgServiceAccept:
+ msg = new(serviceAcceptMsg)
+ case msgKexInit:
+ msg = new(kexInitMsg)
+ case msgKexDHInit:
+ msg = new(kexDHInitMsg)
+ case msgKexDHReply:
+ msg = new(kexDHReplyMsg)
+ case msgUserAuthRequest:
+ msg = new(userAuthRequestMsg)
+ case msgUserAuthFailure:
+ msg = new(userAuthFailureMsg)
+ case msgUserAuthPubKeyOk:
+ msg = new(userAuthPubKeyOkMsg)
+ case msgGlobalRequest:
+ msg = new(globalRequestMsg)
+ case msgRequestSuccess:
+ msg = new(channelRequestSuccessMsg)
+ case msgRequestFailure:
+ msg = new(channelRequestFailureMsg)
+ case msgChannelOpen:
+ msg = new(channelOpenMsg)
+ case msgChannelOpenConfirm:
+ msg = new(channelOpenConfirmMsg)
+ case msgChannelOpenFailure:
+ msg = new(channelOpenFailureMsg)
+ case msgChannelWindowAdjust:
+ msg = new(windowAdjustMsg)
+ case msgChannelEOF:
+ msg = new(channelEOFMsg)
+ case msgChannelClose:
+ msg = new(channelCloseMsg)
+ case msgChannelRequest:
+ msg = new(channelRequestMsg)
+ case msgChannelSuccess:
+ msg = new(channelRequestSuccessMsg)
+ case msgChannelFailure:
+ msg = new(channelRequestFailureMsg)
+ default:
+ return UnexpectedMessageError{0, packet[0]}
+ }
+ if err := unmarshal(msg, packet, packet[0]); err != nil {
+ return err
+ }
+ return msg
+}
diff --git a/ssh/messages_test.go b/ssh/messages_test.go
new file mode 100644
index 0000000..fe4c397
--- /dev/null
+++ b/ssh/messages_test.go
@@ -0,0 +1,125 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "math/big"
+ "math/rand"
+ "reflect"
+ "testing"
+ "testing/quick"
+)
+
+var intLengthTests = []struct {
+ val, length int
+}{
+ {0, 4 + 0},
+ {1, 4 + 1},
+ {127, 4 + 1},
+ {128, 4 + 2},
+ {-1, 4 + 1},
+}
+
+func TestIntLength(t *testing.T) {
+ for _, test := range intLengthTests {
+ v := new(big.Int).SetInt64(int64(test.val))
+ length := intLength(v)
+ if length != test.length {
+ t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
+ }
+ }
+}
+
+var messageTypes = []interface{}{
+ &kexInitMsg{},
+ &kexDHInitMsg{},
+ &serviceRequestMsg{},
+ &serviceAcceptMsg{},
+ &userAuthRequestMsg{},
+ &channelOpenMsg{},
+ &channelOpenConfirmMsg{},
+ &channelRequestMsg{},
+ &channelRequestSuccessMsg{},
+}
+
+func TestMarshalUnmarshal(t *testing.T) {
+ rand := rand.New(rand.NewSource(0))
+ for i, iface := range messageTypes {
+ ty := reflect.ValueOf(iface).Type()
+
+ n := 100
+ if testing.Short() {
+ n = 5
+ }
+ for j := 0; j < n; j++ {
+ v, ok := quick.Value(ty, rand)
+ if !ok {
+ t.Errorf("#%d: failed to create value", i)
+ break
+ }
+
+ m1 := v.Elem().Interface()
+ m2 := iface
+
+ marshaled := marshal(msgIgnore, m1)
+ if err := unmarshal(m2, marshaled, msgIgnore); err != nil {
+ t.Errorf("#%d failed to unmarshal %#v: %s", i, m1, err)
+ break
+ }
+
+ if !reflect.DeepEqual(v.Interface(), m2) {
+ t.Errorf("#%d\ngot: %#v\nwant:%#v\n%x", i, m2, m1, marshaled)
+ break
+ }
+ }
+ }
+}
+
+func randomBytes(out []byte, rand *rand.Rand) {
+ for i := 0; i < len(out); i++ {
+ out[i] = byte(rand.Int31())
+ }
+}
+
+func randomNameList(rand *rand.Rand) []string {
+ ret := make([]string, rand.Int31()&15)
+ for i := range ret {
+ s := make([]byte, 1+(rand.Int31()&15))
+ for j := range s {
+ s[j] = 'a' + uint8(rand.Int31()&15)
+ }
+ ret[i] = string(s)
+ }
+ return ret
+}
+
+func randomInt(rand *rand.Rand) *big.Int {
+ return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
+}
+
+func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
+ ki := &kexInitMsg{}
+ randomBytes(ki.Cookie[:], rand)
+ ki.KexAlgos = randomNameList(rand)
+ ki.ServerHostKeyAlgos = randomNameList(rand)
+ ki.CiphersClientServer = randomNameList(rand)
+ ki.CiphersServerClient = randomNameList(rand)
+ ki.MACsClientServer = randomNameList(rand)
+ ki.MACsServerClient = randomNameList(rand)
+ ki.CompressionClientServer = randomNameList(rand)
+ ki.CompressionServerClient = randomNameList(rand)
+ ki.LanguagesClientServer = randomNameList(rand)
+ ki.LanguagesServerClient = randomNameList(rand)
+ if rand.Int31()&1 == 1 {
+ ki.FirstKexFollows = true
+ }
+ return reflect.ValueOf(ki)
+}
+
+func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
+ dhi := &kexDHInitMsg{}
+ dhi.X = randomInt(rand)
+ return reflect.ValueOf(dhi)
+}
diff --git a/ssh/server.go b/ssh/server.go
new file mode 100644
index 0000000..31011c6
--- /dev/null
+++ b/ssh/server.go
@@ -0,0 +1,676 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+ "io"
+ "math/big"
+ "net"
+ "sync"
+)
+
+type ServerConfig struct {
+ rsa *rsa.PrivateKey
+ rsaSerialized []byte
+
+ // Rand provides the source of entropy for key exchange. If Rand is
+ // nil, the cryptographic random reader in package crypto/rand will
+ // be used.
+ Rand io.Reader
+
+ // NoClientAuth is true if clients are allowed to connect without
+ // authenticating.
+ NoClientAuth bool
+
+ // PasswordCallback, if non-nil, is called when a user attempts to
+ // authenticate using a password. It may be called concurrently from
+ // several goroutines.
+ PasswordCallback func(user, password string) bool
+
+ // PublicKeyCallback, if non-nil, is called when a client attempts public
+ // key authentication. It must return true iff the given public key is
+ // valid for the given user.
+ PublicKeyCallback func(user, algo string, pubkey []byte) bool
+
+ // Cryptographic-related configuration.
+ Crypto CryptoConfig
+}
+
+func (c *ServerConfig) rand() io.Reader {
+ if c.Rand == nil {
+ return rand.Reader
+ }
+ return c.Rand
+}
+
+// SetRSAPrivateKey sets the private key for a Server. A Server must have a
+// private key configured in order to accept connections. The private key must
+// be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa"
+// typically contains such a key.
+func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error {
+ block, _ := pem.Decode(pemBytes)
+ if block == nil {
+ return errors.New("ssh: no key found")
+ }
+ var err error
+ s.rsa, err = x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+
+ s.rsaSerialized = marshalRSA(s.rsa)
+ return nil
+}
+
+// marshalRSA serializes an RSA private key according to RFC 4256, section 6.6.
+func marshalRSA(priv *rsa.PrivateKey) []byte {
+ e := new(big.Int).SetInt64(int64(priv.E))
+ length := stringLength([]byte(hostAlgoRSA))
+ length += intLength(e)
+ length += intLength(priv.N)
+
+ ret := make([]byte, length)
+ r := marshalString(ret, []byte(hostAlgoRSA))
+ r = marshalInt(r, e)
+ r = marshalInt(r, priv.N)
+
+ return ret
+}
+
+// parseRSA parses an RSA key according to RFC 4256, section 6.6.
+func parseRSA(in []byte) (pubKey *rsa.PublicKey, ok bool) {
+ algo, in, ok := parseString(in)
+ if !ok || string(algo) != hostAlgoRSA {
+ return nil, false
+ }
+ bigE, in, ok := parseInt(in)
+ if !ok || bigE.BitLen() > 24 {
+ return nil, false
+ }
+ e := bigE.Int64()
+ if e < 3 || e&1 == 0 {
+ return nil, false
+ }
+ N, in, ok := parseInt(in)
+ if !ok || len(in) > 0 {
+ return nil, false
+ }
+ return &rsa.PublicKey{
+ N: N,
+ E: int(e),
+ }, true
+}
+
+func parseRSASig(in []byte) (sig []byte, ok bool) {
+ algo, in, ok := parseString(in)
+ if !ok || string(algo) != hostAlgoRSA {
+ return nil, false
+ }
+ sig, in, ok = parseString(in)
+ if len(in) > 0 {
+ ok = false
+ }
+ return
+}
+
+// cachedPubKey contains the results of querying whether a public key is
+// acceptable for a user. The cache only applies to a single ServerConn.
+type cachedPubKey struct {
+ user, algo string
+ pubKey []byte
+ result bool
+}
+
+const maxCachedPubKeys = 16
+
+// A ServerConn represents an incomming connection.
+type ServerConn struct {
+ *transport
+ config *ServerConfig
+
+ channels map[uint32]*channel
+ nextChanId uint32
+
+ // lock protects err and also allows Channels to serialise their writes
+ // to out.
+ lock sync.RWMutex
+ err error
+
+ // cachedPubKeys contains the cache results of tests for public keys.
+ // Since SSH clients will query whether a public key is acceptable
+ // before attempting to authenticate with it, we end up with duplicate
+ // queries for public key validity.
+ cachedPubKeys []cachedPubKey
+}
+
+// Server returns a new SSH server connection
+// using c as the underlying transport.
+func Server(c net.Conn, config *ServerConfig) *ServerConn {
+ conn := &ServerConn{
+ transport: newTransport(c, config.rand()),
+ channels: make(map[uint32]*channel),
+ config: config,
+ }
+ return conn
+}
+
+// kexDH performs Diffie-Hellman key agreement on a ServerConnection. The
+// returned values are given the same names as in RFC 4253, section 8.
+func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) (H, K []byte, err error) {
+ packet, err := s.readPacket()
+ if err != nil {
+ return
+ }
+ var kexDHInit kexDHInitMsg
+ if err = unmarshal(&kexDHInit, packet, msgKexDHInit); err != nil {
+ return
+ }
+
+ if kexDHInit.X.Sign() == 0 || kexDHInit.X.Cmp(group.p) >= 0 {
+ return nil, nil, errors.New("client DH parameter out of bounds")
+ }
+
+ y, err := rand.Int(s.config.rand(), group.p)
+ if err != nil {
+ return
+ }
+
+ Y := new(big.Int).Exp(group.g, y, group.p)
+ kInt := new(big.Int).Exp(kexDHInit.X, y, group.p)
+
+ var serializedHostKey []byte
+ switch hostKeyAlgo {
+ case hostAlgoRSA:
+ serializedHostKey = s.config.rsaSerialized
+ default:
+ return nil, nil, errors.New("internal error")
+ }
+
+ h := hashFunc.New()
+ writeString(h, magics.clientVersion)
+ writeString(h, magics.serverVersion)
+ writeString(h, magics.clientKexInit)
+ writeString(h, magics.serverKexInit)
+ writeString(h, serializedHostKey)
+ writeInt(h, kexDHInit.X)
+ writeInt(h, Y)
+ K = make([]byte, intLength(kInt))
+ marshalInt(K, kInt)
+ h.Write(K)
+
+ H = h.Sum(nil)
+
+ h.Reset()
+ h.Write(H)
+ hh := h.Sum(nil)
+
+ var sig []byte
+ switch hostKeyAlgo {
+ case hostAlgoRSA:
+ sig, err = rsa.SignPKCS1v15(s.config.rand(), s.config.rsa, hashFunc, hh)
+ if err != nil {
+ return
+ }
+ default:
+ return nil, nil, errors.New("internal error")
+ }
+
+ serializedSig := serializeSignature(hostAlgoRSA, sig)
+
+ kexDHReply := kexDHReplyMsg{
+ HostKey: serializedHostKey,
+ Y: Y,
+ Signature: serializedSig,
+ }
+ packet = marshal(msgKexDHReply, kexDHReply)
+
+ err = s.writePacket(packet)
+ return
+}
+
+// serverVersion is the fixed identification string that Server will use.
+var serverVersion = []byte("SSH-2.0-Go\r\n")
+
+// Handshake performs an SSH transport and client authentication on the given ServerConn.
+func (s *ServerConn) Handshake() error {
+ var magics handshakeMagics
+ if _, err := s.Write(serverVersion); err != nil {
+ return err
+ }
+ if err := s.Flush(); err != nil {
+ return err
+ }
+ magics.serverVersion = serverVersion[:len(serverVersion)-2]
+
+ version, err := readVersion(s)
+ if err != nil {
+ return err
+ }
+ magics.clientVersion = version
+
+ serverKexInit := kexInitMsg{
+ KexAlgos: supportedKexAlgos,
+ ServerHostKeyAlgos: supportedHostKeyAlgos,
+ CiphersClientServer: s.config.Crypto.ciphers(),
+ CiphersServerClient: s.config.Crypto.ciphers(),
+ MACsClientServer: supportedMACs,
+ MACsServerClient: supportedMACs,
+ CompressionClientServer: supportedCompressions,
+ CompressionServerClient: supportedCompressions,
+ }
+ kexInitPacket := marshal(msgKexInit, serverKexInit)
+ magics.serverKexInit = kexInitPacket
+
+ if err := s.writePacket(kexInitPacket); err != nil {
+ return err
+ }
+
+ packet, err := s.readPacket()
+ if err != nil {
+ return err
+ }
+
+ magics.clientKexInit = packet
+
+ var clientKexInit kexInitMsg
+ if err = unmarshal(&clientKexInit, packet, msgKexInit); err != nil {
+ return err
+ }
+
+ kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(s.transport, &clientKexInit, &serverKexInit)
+ if !ok {
+ return errors.New("ssh: no common algorithms")
+ }
+
+ if clientKexInit.FirstKexFollows && kexAlgo != clientKexInit.KexAlgos[0] {
+ // The client sent a Kex message for the wrong algorithm,
+ // which we have to ignore.
+ if _, err := s.readPacket(); err != nil {
+ return err
+ }
+ }
+
+ var H, K []byte
+ var hashFunc crypto.Hash
+ switch kexAlgo {
+ case kexAlgoDH14SHA1:
+ hashFunc = crypto.SHA1
+ dhGroup14Once.Do(initDHGroup14)
+ H, K, err = s.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
+ default:
+ err = errors.New("ssh: unexpected key exchange algorithm " + kexAlgo)
+ }
+ if err != nil {
+ return err
+ }
+
+ if err = s.writePacket([]byte{msgNewKeys}); err != nil {
+ return err
+ }
+ if err = s.transport.writer.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
+ return err
+ }
+ if packet, err = s.readPacket(); err != nil {
+ return err
+ }
+
+ if packet[0] != msgNewKeys {
+ return UnexpectedMessageError{msgNewKeys, packet[0]}
+ }
+ if err = s.transport.reader.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
+ return err
+ }
+ if packet, err = s.readPacket(); err != nil {
+ return err
+ }
+
+ var serviceRequest serviceRequestMsg
+ if err = unmarshal(&serviceRequest, packet, msgServiceRequest); err != nil {
+ return err
+ }
+ if serviceRequest.Service != serviceUserAuth {
+ return errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
+ }
+ serviceAccept := serviceAcceptMsg{
+ Service: serviceUserAuth,
+ }
+ if err = s.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil {
+ return err
+ }
+
+ if err = s.authenticate(H); err != nil {
+ return err
+ }
+ return nil
+}
+
+func isAcceptableAlgo(algo string) bool {
+ return algo == hostAlgoRSA
+}
+
+// testPubKey returns true if the given public key is acceptable for the user.
+func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool {
+ if s.config.PublicKeyCallback == nil || !isAcceptableAlgo(algo) {
+ return false
+ }
+
+ for _, c := range s.cachedPubKeys {
+ if c.user == user && c.algo == algo && bytes.Equal(c.pubKey, pubKey) {
+ return c.result
+ }
+ }
+
+ result := s.config.PublicKeyCallback(user, algo, pubKey)
+ if len(s.cachedPubKeys) < maxCachedPubKeys {
+ c := cachedPubKey{
+ user: user,
+ algo: algo,
+ pubKey: make([]byte, len(pubKey)),
+ result: result,
+ }
+ copy(c.pubKey, pubKey)
+ s.cachedPubKeys = append(s.cachedPubKeys, c)
+ }
+
+ return result
+}
+
+func (s *ServerConn) authenticate(H []byte) error {
+ var userAuthReq userAuthRequestMsg
+ var err error
+ var packet []byte
+
+userAuthLoop:
+ for {
+ if packet, err = s.readPacket(); err != nil {
+ return err
+ }
+ if err = unmarshal(&userAuthReq, packet, msgUserAuthRequest); err != nil {
+ return err
+ }
+
+ if userAuthReq.Service != serviceSSH {
+ return errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
+ }
+
+ switch userAuthReq.Method {
+ case "none":
+ if s.config.NoClientAuth {
+ break userAuthLoop
+ }
+ case "password":
+ if s.config.PasswordCallback == nil {
+ break
+ }
+ payload := userAuthReq.Payload
+ if len(payload) < 1 || payload[0] != 0 {
+ return ParseError{msgUserAuthRequest}
+ }
+ payload = payload[1:]
+ password, payload, ok := parseString(payload)
+ if !ok || len(payload) > 0 {
+ return ParseError{msgUserAuthRequest}
+ }
+
+ if s.config.PasswordCallback(userAuthReq.User, string(password)) {
+ break userAuthLoop
+ }
+ case "publickey":
+ if s.config.PublicKeyCallback == nil {
+ break
+ }
+ payload := userAuthReq.Payload
+ if len(payload) < 1 {
+ return ParseError{msgUserAuthRequest}
+ }
+ isQuery := payload[0] == 0
+ payload = payload[1:]
+ algoBytes, payload, ok := parseString(payload)
+ if !ok {
+ return ParseError{msgUserAuthRequest}
+ }
+ algo := string(algoBytes)
+
+ pubKey, payload, ok := parseString(payload)
+ if !ok {
+ return ParseError{msgUserAuthRequest}
+ }
+ if isQuery {
+ // The client can query if the given public key
+ // would be ok.
+ if len(payload) > 0 {
+ return ParseError{msgUserAuthRequest}
+ }
+ if s.testPubKey(userAuthReq.User, algo, pubKey) {
+ okMsg := userAuthPubKeyOkMsg{
+ Algo: algo,
+ PubKey: string(pubKey),
+ }
+ if err = s.writePacket(marshal(msgUserAuthPubKeyOk, okMsg)); err != nil {
+ return err
+ }
+ continue userAuthLoop
+ }
+ } else {
+ sig, payload, ok := parseString(payload)
+ if !ok || len(payload) > 0 {
+ return ParseError{msgUserAuthRequest}
+ }
+ if !isAcceptableAlgo(algo) {
+ break
+ }
+ rsaSig, ok := parseRSASig(sig)
+ if !ok {
+ return ParseError{msgUserAuthRequest}
+ }
+ signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey)
+ switch algo {
+ case hostAlgoRSA:
+ hashFunc := crypto.SHA1
+ h := hashFunc.New()
+ h.Write(signedData)
+ digest := h.Sum(nil)
+ rsaKey, ok := parseRSA(pubKey)
+ if !ok {
+ return ParseError{msgUserAuthRequest}
+ }
+ if rsa.VerifyPKCS1v15(rsaKey, hashFunc, digest, rsaSig) != nil {
+ return ParseError{msgUserAuthRequest}
+ }
+ default:
+ return errors.New("ssh: isAcceptableAlgo incorrect")
+ }
+ if s.testPubKey(userAuthReq.User, algo, pubKey) {
+ break userAuthLoop
+ }
+ }
+ }
+
+ var failureMsg userAuthFailureMsg
+ if s.config.PasswordCallback != nil {
+ failureMsg.Methods = append(failureMsg.Methods, "password")
+ }
+ if s.config.PublicKeyCallback != nil {
+ failureMsg.Methods = append(failureMsg.Methods, "publickey")
+ }
+
+ if len(failureMsg.Methods) == 0 {
+ return errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
+ }
+
+ if err = s.writePacket(marshal(msgUserAuthFailure, failureMsg)); err != nil {
+ return err
+ }
+ }
+
+ packet = []byte{msgUserAuthSuccess}
+ if err = s.writePacket(packet); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+const defaultWindowSize = 32768
+
+// Accept reads and processes messages on a ServerConn. It must be called
+// in order to demultiplex messages to any resulting Channels.
+func (s *ServerConn) Accept() (Channel, error) {
+ if s.err != nil {
+ return nil, s.err
+ }
+
+ for {
+ packet, err := s.readPacket()
+ if err != nil {
+
+ s.lock.Lock()
+ s.err = err
+ s.lock.Unlock()
+
+ for _, c := range s.channels {
+ c.dead = true
+ c.handleData(nil)
+ }
+
+ return nil, err
+ }
+
+ switch packet[0] {
+ case msgChannelData:
+ if len(packet) < 9 {
+ // malformed data packet
+ return nil, ParseError{msgChannelData}
+ }
+ peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
+ s.lock.Lock()
+ c, ok := s.channels[peersId]
+ if !ok {
+ s.lock.Unlock()
+ continue
+ }
+ if length := int(packet[5])<<24 | int(packet[6])<<16 | int(packet[7])<<8 | int(packet[8]); length > 0 {
+ packet = packet[9:]
+ c.handleData(packet[:length])
+ }
+ s.lock.Unlock()
+ default:
+ switch msg := decode(packet).(type) {
+ case *channelOpenMsg:
+ c := new(channel)
+ c.chanType = msg.ChanType
+ c.theirId = msg.PeersId
+ c.theirWindow = msg.PeersWindow
+ c.maxPacketSize = msg.MaxPacketSize
+ c.extraData = msg.TypeSpecificData
+ c.myWindow = defaultWindowSize
+ c.serverConn = s
+ c.cond = sync.NewCond(&c.lock)
+ c.pendingData = make([]byte, c.myWindow)
+
+ s.lock.Lock()
+ c.myId = s.nextChanId
+ s.nextChanId++
+ s.channels[c.myId] = c
+ s.lock.Unlock()
+ return c, nil
+
+ case *channelRequestMsg:
+ s.lock.Lock()
+ c, ok := s.channels[msg.PeersId]
+ if !ok {
+ s.lock.Unlock()
+ continue
+ }
+ c.handlePacket(msg)
+ s.lock.Unlock()
+
+ case *channelEOFMsg:
+ s.lock.Lock()
+ c, ok := s.channels[msg.PeersId]
+ if !ok {
+ s.lock.Unlock()
+ continue
+ }
+ c.handlePacket(msg)
+ s.lock.Unlock()
+
+ case *channelCloseMsg:
+ s.lock.Lock()
+ c, ok := s.channels[msg.PeersId]
+ if !ok {
+ s.lock.Unlock()
+ continue
+ }
+ c.handlePacket(msg)
+ s.lock.Unlock()
+
+ case *globalRequestMsg:
+ if msg.WantReply {
+ if err := s.writePacket([]byte{msgRequestFailure}); err != nil {
+ return nil, err
+ }
+ }
+
+ case UnexpectedMessageError:
+ return nil, msg
+ case *disconnectMsg:
+ return nil, io.EOF
+ default:
+ // Unknown message. Ignore.
+ }
+ }
+ }
+
+ panic("unreachable")
+}
+
+// A Listener implements a network listener (net.Listener) for SSH connections.
+type Listener struct {
+ listener net.Listener
+ config *ServerConfig
+}
+
+// Accept waits for and returns the next incoming SSH connection.
+// The receiver should call Handshake() in another goroutine
+// to avoid blocking the accepter.
+func (l *Listener) Accept() (*ServerConn, error) {
+ c, err := l.listener.Accept()
+ if err != nil {
+ return nil, err
+ }
+ conn := Server(c, l.config)
+ return conn, nil
+}
+
+// Addr returns the listener's network address.
+func (l *Listener) Addr() net.Addr {
+ return l.listener.Addr()
+}
+
+// Close closes the listener.
+func (l *Listener) Close() error {
+ return l.listener.Close()
+}
+
+// Listen creates an SSH listener accepting connections on
+// the given network address using net.Listen.
+func Listen(network, addr string, config *ServerConfig) (*Listener, error) {
+ l, err := net.Listen(network, addr)
+ if err != nil {
+ return nil, err
+ }
+ return &Listener{
+ l,
+ config,
+ }, nil
+}
diff --git a/ssh/server_terminal.go b/ssh/server_terminal.go
new file mode 100644
index 0000000..708a915
--- /dev/null
+++ b/ssh/server_terminal.go
@@ -0,0 +1,81 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// A Terminal is capable of parsing and generating virtual terminal
+// data from an SSH client.
+type Terminal interface {
+ ReadLine() (line string, err error)
+ SetSize(x, y int)
+ Write([]byte) (int, error)
+}
+
+// ServerTerminal contains the state for running a terminal that is capable of
+// reading lines of input.
+type ServerTerminal struct {
+ Term Terminal
+ Channel Channel
+}
+
+// parsePtyRequest parses the payload of the pty-req message and extracts the
+// dimensions of the terminal. See RFC 4254, section 6.2.
+func parsePtyRequest(s []byte) (width, height int, ok bool) {
+ _, s, ok = parseString(s)
+ if !ok {
+ return
+ }
+ width32, s, ok := parseUint32(s)
+ if !ok {
+ return
+ }
+ height32, _, ok := parseUint32(s)
+ width = int(width32)
+ height = int(height32)
+ if width < 1 {
+ ok = false
+ }
+ if height < 1 {
+ ok = false
+ }
+ return
+}
+
+func (ss *ServerTerminal) Write(buf []byte) (n int, err error) {
+ return ss.Term.Write(buf)
+}
+
+// ReadLine returns a line of input from the terminal.
+func (ss *ServerTerminal) ReadLine() (line string, err error) {
+ for {
+ if line, err = ss.Term.ReadLine(); err == nil {
+ return
+ }
+
+ req, ok := err.(ChannelRequest)
+ if !ok {
+ return
+ }
+
+ ok = false
+ switch req.Request {
+ case "pty-req":
+ var width, height int
+ width, height, ok = parsePtyRequest(req.Payload)
+ ss.Term.SetSize(width, height)
+ case "shell":
+ ok = true
+ if len(req.Payload) > 0 {
+ // We don't accept any commands, only the default shell.
+ ok = false
+ }
+ case "env":
+ ok = true
+ }
+ if req.WantReply {
+ ss.Channel.AckRequest(ok)
+ }
+ }
+ panic("unreachable")
+}
diff --git a/ssh/session.go b/ssh/session.go
new file mode 100644
index 0000000..ea4addb
--- /dev/null
+++ b/ssh/session.go
@@ -0,0 +1,494 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// Session implements an interactive session described in
+// "RFC 4254, section 6".
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+)
+
+type Signal string
+
+// POSIX signals as listed in RFC 4254 Section 6.10.
+const (
+ SIGABRT Signal = "ABRT"
+ SIGALRM Signal = "ALRM"
+ SIGFPE Signal = "FPE"
+ SIGHUP Signal = "HUP"
+ SIGILL Signal = "ILL"
+ SIGINT Signal = "INT"
+ SIGKILL Signal = "KILL"
+ SIGPIPE Signal = "PIPE"
+ SIGQUIT Signal = "QUIT"
+ SIGSEGV Signal = "SEGV"
+ SIGTERM Signal = "TERM"
+ SIGUSR1 Signal = "USR1"
+ SIGUSR2 Signal = "USR2"
+)
+
+var signals = map[Signal]int{
+ SIGABRT: 6,
+ SIGALRM: 14,
+ SIGFPE: 8,
+ SIGHUP: 1,
+ SIGILL: 4,
+ SIGINT: 2,
+ SIGKILL: 9,
+ SIGPIPE: 13,
+ SIGQUIT: 3,
+ SIGSEGV: 11,
+ SIGTERM: 15,
+}
+
+// A Session represents a connection to a remote command or shell.
+type Session struct {
+ // Stdin specifies the remote process's standard input.
+ // If Stdin is nil, the remote process reads from an empty
+ // bytes.Buffer.
+ Stdin io.Reader
+
+ // Stdout and Stderr specify the remote process's standard
+ // output and error.
+ //
+ // If either is nil, Run connects the corresponding file
+ // descriptor to an instance of ioutil.Discard. There is a
+ // fixed amount of buffering that is shared for the two streams.
+ // If either blocks it may eventually cause the remote
+ // command to block.
+ Stdout io.Writer
+ Stderr io.Writer
+
+ *clientChan // the channel backing this session
+
+ started bool // true once Start, Run or Shell is invoked.
+ copyFuncs []func() error
+ errors chan error // one send per copyFunc
+
+ // true if pipe method is active
+ stdinpipe, stdoutpipe, stderrpipe bool
+}
+
+// RFC 4254 Section 6.4.
+type setenvRequest struct {
+ PeersId uint32
+ Request string
+ WantReply bool
+ Name string
+ Value string
+}
+
+// Setenv sets an environment variable that will be applied to any
+// command executed by Shell or Run.
+func (s *Session) Setenv(name, value string) error {
+ req := setenvRequest{
+ PeersId: s.peersId,
+ Request: "env",
+ WantReply: true,
+ Name: name,
+ Value: value,
+ }
+ if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+ return err
+ }
+ return s.waitForResponse()
+}
+
+// An empty mode list, see RFC 4254 Section 8.
+var emptyModelist = "\x00"
+
+// RFC 4254 Section 6.2.
+type ptyRequestMsg struct {
+ PeersId uint32
+ Request string
+ WantReply bool
+ Term string
+ Columns uint32
+ Rows uint32
+ Width uint32
+ Height uint32
+ Modelist string
+}
+
+// RequestPty requests the association of a pty with the session on the remote host.
+func (s *Session) RequestPty(term string, h, w int) error {
+ req := ptyRequestMsg{
+ PeersId: s.peersId,
+ Request: "pty-req",
+ WantReply: true,
+ Term: term,
+ Columns: uint32(w),
+ Rows: uint32(h),
+ Width: uint32(w * 8),
+ Height: uint32(h * 8),
+ Modelist: emptyModelist,
+ }
+ if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+ return err
+ }
+ return s.waitForResponse()
+}
+
+// RFC 4254 Section 6.9.
+type signalMsg struct {
+ PeersId uint32
+ Request string
+ WantReply bool
+ Signal string
+}
+
+// Signal sends the given signal to the remote process.
+// sig is one of the SIG* constants.
+func (s *Session) Signal(sig Signal) error {
+ req := signalMsg{
+ PeersId: s.peersId,
+ Request: "signal",
+ WantReply: false,
+ Signal: string(sig),
+ }
+ return s.writePacket(marshal(msgChannelRequest, req))
+}
+
+// RFC 4254 Section 6.5.
+type execMsg struct {
+ PeersId uint32
+ Request string
+ WantReply bool
+ Command string
+}
+
+// Start runs cmd on the remote host. Typically, the remote
+// server passes cmd to the shell for interpretation.
+// A Session only accepts one call to Run, Start or Shell.
+func (s *Session) Start(cmd string) error {
+ if s.started {
+ return errors.New("ssh: session already started")
+ }
+ req := execMsg{
+ PeersId: s.peersId,
+ Request: "exec",
+ WantReply: true,
+ Command: cmd,
+ }
+ if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+ return err
+ }
+ if err := s.waitForResponse(); err != nil {
+ return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)
+ }
+ return s.start()
+}
+
+// Run runs cmd on the remote host. Typically, the remote
+// server passes cmd to the shell for interpretation.
+// A Session only accepts one call to Run, Start or Shell.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *ExitError. Other error types may be
+// returned for I/O problems.
+func (s *Session) Run(cmd string) error {
+ err := s.Start(cmd)
+ if err != nil {
+ return err
+ }
+ return s.Wait()
+}
+
+// Shell starts a login shell on the remote host. A Session only
+// accepts one call to Run, Start or Shell.
+func (s *Session) Shell() error {
+ if s.started {
+ return errors.New("ssh: session already started")
+ }
+ req := channelRequestMsg{
+ PeersId: s.peersId,
+ Request: "shell",
+ WantReply: true,
+ }
+ if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+ return err
+ }
+ if err := s.waitForResponse(); err != nil {
+ return fmt.Errorf("ssh: cound not execute shell: %v", err)
+ }
+ return s.start()
+}
+
+func (s *Session) waitForResponse() error {
+ msg := <-s.msg
+ switch msg.(type) {
+ case *channelRequestSuccessMsg:
+ return nil
+ case *channelRequestFailureMsg:
+ return errors.New("request failed")
+ }
+ return fmt.Errorf("unknown packet %T received: %v", msg, msg)
+}
+
+func (s *Session) start() error {
+ s.started = true
+
+ type F func(*Session)
+ for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
+ setupFd(s)
+ }
+
+ s.errors = make(chan error, len(s.copyFuncs))
+ for _, fn := range s.copyFuncs {
+ go func(fn func() error) {
+ s.errors <- fn()
+ }(fn)
+ }
+ return nil
+}
+
+// Wait waits for the remote command to exit.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *ExitError. Other error types may be
+// returned for I/O problems.
+func (s *Session) Wait() error {
+ if !s.started {
+ return errors.New("ssh: session not started")
+ }
+ waitErr := s.wait()
+
+ var copyError error
+ for _ = range s.copyFuncs {
+ if err := <-s.errors; err != nil && copyError == nil {
+ copyError = err
+ }
+ }
+ if waitErr != nil {
+ return waitErr
+ }
+ return copyError
+}
+
+func (s *Session) wait() error {
+ wm := Waitmsg{status: -1}
+
+ // Wait for msg channel to be closed before returning.
+ for msg := range s.msg {
+ switch msg := msg.(type) {
+ case *channelRequestMsg:
+ switch msg.Request {
+ case "exit-status":
+ d := msg.RequestSpecificData
+ wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
+ case "exit-signal":
+ signal, rest, ok := parseString(msg.RequestSpecificData)
+ if !ok {
+ return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+ }
+ wm.signal = safeString(string(signal))
+
+ // skip coreDumped bool
+ if len(rest) == 0 {
+ return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+ }
+ rest = rest[1:]
+
+ errmsg, rest, ok := parseString(rest)
+ if !ok {
+ return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+ }
+ wm.msg = safeString(string(errmsg))
+
+ lang, _, ok := parseString(rest)
+ if !ok {
+ return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+ }
+ wm.lang = safeString(string(lang))
+ default:
+ return fmt.Errorf("wait: unexpected channel request: %v", msg)
+ }
+ default:
+ return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg)
+ }
+ }
+ if wm.status == 0 {
+ return nil
+ }
+ if wm.status == -1 {
+ // exit-status was never sent from server
+ if wm.signal == "" {
+ return errors.New("wait: remote command exited without exit status or exit signal")
+ }
+ wm.status = 128
+ if _, ok := signals[Signal(wm.signal)]; ok {
+ wm.status += signals[Signal(wm.signal)]
+ }
+ }
+ return &ExitError{wm}
+}
+
+func (s *Session) stdin() {
+ if s.stdinpipe {
+ return
+ }
+ if s.Stdin == nil {
+ s.Stdin = new(bytes.Buffer)
+ }
+ s.copyFuncs = append(s.copyFuncs, func() error {
+ _, err := io.Copy(s.clientChan.stdin, s.Stdin)
+ if err1 := s.clientChan.stdin.Close(); err == nil {
+ err = err1
+ }
+ return err
+ })
+}
+
+func (s *Session) stdout() {
+ if s.stdoutpipe {
+ return
+ }
+ if s.Stdout == nil {
+ s.Stdout = ioutil.Discard
+ }
+ s.copyFuncs = append(s.copyFuncs, func() error {
+ _, err := io.Copy(s.Stdout, s.clientChan.stdout)
+ return err
+ })
+}
+
+func (s *Session) stderr() {
+ if s.stderrpipe {
+ return
+ }
+ if s.Stderr == nil {
+ s.Stderr = ioutil.Discard
+ }
+ s.copyFuncs = append(s.copyFuncs, func() error {
+ _, err := io.Copy(s.Stderr, s.clientChan.stderr)
+ return err
+ })
+}
+
+// StdinPipe returns a pipe that will be connected to the
+// remote command's standard input when the command starts.
+func (s *Session) StdinPipe() (io.WriteCloser, error) {
+ if s.Stdin != nil {
+ return nil, errors.New("ssh: Stdin already set")
+ }
+ if s.started {
+ return nil, errors.New("ssh: StdinPipe after process started")
+ }
+ s.stdinpipe = true
+ return s.clientChan.stdin, nil
+}
+
+// StdoutPipe returns a pipe that will be connected to the
+// remote command's standard output when the command starts.
+// There is a fixed amount of buffering that is shared between
+// stdout and stderr streams. If the StdoutPipe reader is
+// not serviced fast enought it may eventually cause the
+// remote command to block.
+func (s *Session) StdoutPipe() (io.Reader, error) {
+ if s.Stdout != nil {
+ return nil, errors.New("ssh: Stdout already set")
+ }
+ if s.started {
+ return nil, errors.New("ssh: StdoutPipe after process started")
+ }
+ s.stdoutpipe = true
+ return s.clientChan.stdout, nil
+}
+
+// StderrPipe returns a pipe that will be connected to the
+// remote command's standard error when the command starts.
+// There is a fixed amount of buffering that is shared between
+// stdout and stderr streams. If the StderrPipe reader is
+// not serviced fast enought it may eventually cause the
+// remote command to block.
+func (s *Session) StderrPipe() (io.Reader, error) {
+ if s.Stderr != nil {
+ return nil, errors.New("ssh: Stderr already set")
+ }
+ if s.started {
+ return nil, errors.New("ssh: StderrPipe after process started")
+ }
+ s.stderrpipe = true
+ return s.clientChan.stderr, nil
+}
+
+// TODO(dfc) add Output and CombinedOutput helpers
+
+// NewSession returns a new interactive session on the remote host.
+func (c *ClientConn) NewSession() (*Session, error) {
+ ch := c.newChan(c.transport)
+ if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{
+ ChanType: "session",
+ PeersId: ch.id,
+ PeersWindow: 1 << 14,
+ MaxPacketSize: 1 << 15, // RFC 4253 6.1
+ })); err != nil {
+ c.chanlist.remove(ch.id)
+ return nil, err
+ }
+ if err := ch.waitForChannelOpenResponse(); err != nil {
+ c.chanlist.remove(ch.id)
+ return nil, fmt.Errorf("ssh: unable to open session: %v", err)
+ }
+ return &Session{
+ clientChan: ch,
+ }, nil
+}
+
+// An ExitError reports unsuccessful completion of a remote command.
+type ExitError struct {
+ Waitmsg
+}
+
+func (e *ExitError) Error() string {
+ return e.Waitmsg.String()
+}
+
+// Waitmsg stores the information about an exited remote command
+// as reported by Wait.
+type Waitmsg struct {
+ status int
+ signal string
+ msg string
+ lang string
+}
+
+// ExitStatus returns the exit status of the remote command.
+func (w Waitmsg) ExitStatus() int {
+ return w.status
+}
+
+// Signal returns the exit signal of the remote command if
+// it was terminated violently.
+func (w Waitmsg) Signal() string {
+ return w.signal
+}
+
+// Msg returns the exit message given by the remote command
+func (w Waitmsg) Msg() string {
+ return w.msg
+}
+
+// Lang returns the language tag. See RFC 3066
+func (w Waitmsg) Lang() string {
+ return w.lang
+}
+
+func (w Waitmsg) String() string {
+ return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
+}
diff --git a/ssh/session_test.go b/ssh/session_test.go
new file mode 100644
index 0000000..4a3d22b
--- /dev/null
+++ b/ssh/session_test.go
@@ -0,0 +1,374 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// Session tests.
+
+import (
+ "bytes"
+ "exp/terminal"
+ "io"
+ "testing"
+)
+
+type serverType func(*channel)
+
+// dial constructs a new test server and returns a *ClientConn.
+func dial(handler serverType, t *testing.T) *ClientConn {
+ pw := password("tiger")
+ serverConfig.PasswordCallback = func(user, pass string) bool {
+ return user == "testuser" && pass == string(pw)
+ }
+ serverConfig.PublicKeyCallback = nil
+
+ l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
+ if err != nil {
+ t.Fatalf("unable to listen: %s", err)
+ }
+ go func() {
+ defer l.Close()
+ conn, err := l.Accept()
+ if err != nil {
+ t.Errorf("Unable to accept: %v", err)
+ return
+ }
+ defer conn.Close()
+ if err := conn.Handshake(); err != nil {
+ t.Errorf("Unable to handshake: %v", err)
+ return
+ }
+ for {
+ ch, err := conn.Accept()
+ if err == io.EOF {
+ return
+ }
+ if err != nil {
+ t.Errorf("Unable to accept incoming channel request: %v", err)
+ return
+ }
+ if ch.ChannelType() != "session" {
+ ch.Reject(UnknownChannelType, "unknown channel type")
+ continue
+ }
+ ch.Accept()
+ go handler(ch.(*channel))
+ }
+ t.Log("done")
+ }()
+
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthPassword(pw),
+ },
+ }
+
+ c, err := Dial("tcp", l.Addr().String(), config)
+ if err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ return c
+}
+
+// Test a simple string is returned to session.Stdout.
+func TestSessionShell(t *testing.T) {
+ conn := dial(shellHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+ stdout := new(bytes.Buffer)
+ session.Stdout = stdout
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ if err := session.Wait(); err != nil {
+ t.Fatalf("Remote command did not exit cleanly: %s", err)
+ }
+ actual := stdout.String()
+ if actual != "golang" {
+ t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
+ }
+}
+
+// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
+
+// Test a simple string is returned via StdoutPipe.
+func TestSessionStdoutPipe(t *testing.T) {
+ conn := dial(shellHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("Unable to request StdoutPipe(): %v", err)
+ }
+ var buf bytes.Buffer
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ done := make(chan bool, 1)
+ go func() {
+ if _, err := io.Copy(&buf, stdout); err != nil {
+ t.Errorf("Copy of stdout failed: %v", err)
+ }
+ done <- true
+ }()
+ if err := session.Wait(); err != nil {
+ t.Fatalf("Remote command did not exit cleanly: %s", err)
+ }
+ <-done
+ actual := buf.String()
+ if actual != "golang" {
+ t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
+ }
+}
+
+// Test non-0 exit status is returned correctly.
+func TestExitStatusNonZero(t *testing.T) {
+ conn := dial(exitStatusNonZeroHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.ExitStatus() != 15 {
+ t.Fatalf("expected command to exit with 15 but got %s", e.ExitStatus())
+ }
+}
+
+// Test 0 exit status is returned correctly.
+func TestExitStatusZero(t *testing.T) {
+ conn := dial(exitStatusZeroHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ err = session.Wait()
+ if err != nil {
+ t.Fatalf("expected nil but got %s", err)
+ }
+}
+
+// Test exit signal and status are both returned correctly.
+func TestExitSignalAndStatus(t *testing.T) {
+ conn := dial(exitSignalAndStatusHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.Signal() != "TERM" || e.ExitStatus() != 15 {
+ t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+ }
+}
+
+// Test exit signal and status are both returned correctly.
+func TestKnownExitSignalOnly(t *testing.T) {
+ conn := dial(exitSignalHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.Signal() != "TERM" || e.ExitStatus() != 143 {
+ t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+ }
+}
+
+// Test exit signal and status are both returned correctly.
+func TestUnknownExitSignal(t *testing.T) {
+ conn := dial(exitSignalUnknownHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.Signal() != "SYS" || e.ExitStatus() != 128 {
+ t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+ }
+}
+
+// Test WaitMsg is not returned if the channel closes abruptly.
+func TestExitWithoutStatusOrSignal(t *testing.T) {
+ conn := dial(exitWithoutSignalOrStatus, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %s", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ _, ok := err.(*ExitError)
+ if ok {
+ // you can't actually test for errors.errorString
+ // because it's not exported.
+ t.Fatalf("expected *errorString but got %T", err)
+ }
+}
+
+type exitStatusMsg struct {
+ PeersId uint32
+ Request string
+ WantReply bool
+ Status uint32
+}
+
+type exitSignalMsg struct {
+ PeersId uint32
+ Request string
+ WantReply bool
+ Signal string
+ CoreDumped bool
+ Errmsg string
+ Lang string
+}
+
+func newServerShell(ch *channel, prompt string) *ServerTerminal {
+ term := terminal.NewTerminal(ch, prompt)
+ return &ServerTerminal{
+ Term: term,
+ Channel: ch,
+ }
+}
+
+func exitStatusZeroHandler(ch *channel) {
+ defer ch.Close()
+ // this string is returned to stdout
+ shell := newServerShell(ch, "> ")
+ shell.ReadLine()
+ sendStatus(0, ch)
+}
+
+func exitStatusNonZeroHandler(ch *channel) {
+ defer ch.Close()
+ shell := newServerShell(ch, "> ")
+ shell.ReadLine()
+ sendStatus(15, ch)
+}
+
+func exitSignalAndStatusHandler(ch *channel) {
+ defer ch.Close()
+ shell := newServerShell(ch, "> ")
+ shell.ReadLine()
+ sendStatus(15, ch)
+ sendSignal("TERM", ch)
+}
+
+func exitSignalHandler(ch *channel) {
+ defer ch.Close()
+ shell := newServerShell(ch, "> ")
+ shell.ReadLine()
+ sendSignal("TERM", ch)
+}
+
+func exitSignalUnknownHandler(ch *channel) {
+ defer ch.Close()
+ shell := newServerShell(ch, "> ")
+ shell.ReadLine()
+ sendSignal("SYS", ch)
+}
+
+func exitWithoutSignalOrStatus(ch *channel) {
+ defer ch.Close()
+ shell := newServerShell(ch, "> ")
+ shell.ReadLine()
+}
+
+func shellHandler(ch *channel) {
+ defer ch.Close()
+ // this string is returned to stdout
+ shell := newServerShell(ch, "golang")
+ shell.ReadLine()
+ sendStatus(0, ch)
+}
+
+func sendStatus(status uint32, ch *channel) {
+ msg := exitStatusMsg{
+ PeersId: ch.theirId,
+ Request: "exit-status",
+ WantReply: false,
+ Status: status,
+ }
+ ch.serverConn.writePacket(marshal(msgChannelRequest, msg))
+}
+
+func sendSignal(signal string, ch *channel) {
+ sig := exitSignalMsg{
+ PeersId: ch.theirId,
+ Request: "exit-signal",
+ WantReply: false,
+ Signal: signal,
+ CoreDumped: false,
+ Errmsg: "Process terminated",
+ Lang: "en-GB-oed",
+ }
+ ch.serverConn.writePacket(marshal(msgChannelRequest, sig))
+}
diff --git a/ssh/tcpip.go b/ssh/tcpip.go
new file mode 100644
index 0000000..e0c47bc
--- /dev/null
+++ b/ssh/tcpip.go
@@ -0,0 +1,132 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "time"
+)
+
+// Dial initiates a connection to the addr from the remote host.
+// addr is resolved using net.ResolveTCPAddr before connection.
+// This could allow an observer to observe the DNS name of the
+// remote host. Consider using ssh.DialTCP to avoid this.
+func (c *ClientConn) Dial(n, addr string) (net.Conn, error) {
+ raddr, err := net.ResolveTCPAddr(n, addr)
+ if err != nil {
+ return nil, err
+ }
+ return c.DialTCP(n, nil, raddr)
+}
+
+// DialTCP connects to the remote address raddr on the network net,
+// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used
+// as the local address for the connection.
+func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
+ if laddr == nil {
+ laddr = &net.TCPAddr{
+ IP: net.IPv4zero,
+ Port: 0,
+ }
+ }
+ ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
+ if err != nil {
+ return nil, err
+ }
+ return &tcpchanconn{
+ tcpchan: ch,
+ laddr: laddr,
+ raddr: raddr,
+ }, nil
+}
+
+// RFC 4254 7.2
+type channelOpenDirectMsg struct {
+ ChanType string
+ PeersId uint32
+ PeersWindow uint32
+ MaxPacketSize uint32
+ raddr string
+ rport uint32
+ laddr string
+ lport uint32
+}
+
+// dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as
+// strings and are expected to be resolveable at the remote end.
+func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpchan, error) {
+ ch := c.newChan(c.transport)
+ if err := c.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{
+ ChanType: "direct-tcpip",
+ PeersId: ch.id,
+ PeersWindow: 1 << 14,
+ MaxPacketSize: 1 << 15, // RFC 4253 6.1
+ raddr: raddr,
+ rport: uint32(rport),
+ laddr: laddr,
+ lport: uint32(lport),
+ })); err != nil {
+ c.chanlist.remove(ch.id)
+ return nil, err
+ }
+ if err := ch.waitForChannelOpenResponse(); err != nil {
+ c.chanlist.remove(ch.id)
+ return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err)
+ }
+ return &tcpchan{
+ clientChan: ch,
+ Reader: ch.stdout,
+ Writer: ch.stdin,
+ }, nil
+}
+
+type tcpchan struct {
+ *clientChan // the backing channel
+ io.Reader
+ io.Writer
+}
+
+// tcpchanconn fulfills the net.Conn interface without
+// the tcpchan having to hold laddr or raddr directly.
+type tcpchanconn struct {
+ *tcpchan
+ laddr, raddr net.Addr
+}
+
+// LocalAddr returns the local network address.
+func (t *tcpchanconn) LocalAddr() net.Addr {
+ return t.laddr
+}
+
+// RemoteAddr returns the remote network address.
+func (t *tcpchanconn) RemoteAddr() net.Addr {
+ return t.raddr
+}
+
+// SetDeadline sets the read and write deadlines associated
+// with the connection.
+func (t *tcpchanconn) SetDeadline(deadline time.Time) error {
+ if err := t.SetReadDeadline(deadline); err != nil {
+ return err
+ }
+ return t.SetWriteDeadline(deadline)
+}
+
+// SetReadDeadline sets the read deadline.
+// A zero value for t means Read will not time out.
+// After the deadline, the error from Read will implement net.Error
+// with Timeout() == true.
+func (t *tcpchanconn) SetReadDeadline(deadline time.Time) error {
+ return errors.New("ssh: tcpchan: deadline not supported")
+}
+
+// SetWriteDeadline exists to satisfy the net.Conn interface
+// but is not implemented by this type. It always returns an error.
+func (t *tcpchanconn) SetWriteDeadline(deadline time.Time) error {
+ return errors.New("ssh: tcpchan: deadline not supported")
+}
diff --git a/ssh/tcpip_func_test.go b/ssh/tcpip_func_test.go
new file mode 100644
index 0000000..2612972
--- /dev/null
+++ b/ssh/tcpip_func_test.go
@@ -0,0 +1,59 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// direct-tcpip functional tests
+
+import (
+ "net"
+ "net/http"
+ "testing"
+)
+
+func TestTCPIPHTTP(t *testing.T) {
+ if *sshuser == "" {
+ t.Log("ssh.user not defined, skipping test")
+ return
+ }
+ // google.com will generate at least one redirect, possibly three
+ // depending on your location.
+ doTest(t, "http://google.com")
+}
+
+func TestTCPIPHTTPS(t *testing.T) {
+ if *sshuser == "" {
+ t.Log("ssh.user not defined, skipping test")
+ return
+ }
+ doTest(t, "https://encrypted.google.com/")
+}
+
+func doTest(t *testing.T, url string) {
+ config := &ClientConfig{
+ User: *sshuser,
+ Auth: []ClientAuth{
+ ClientAuthPassword(password(*sshpass)),
+ },
+ }
+ conn, err := Dial("tcp", "localhost:22", config)
+ if err != nil {
+ t.Fatalf("Unable to connect: %s", err)
+ }
+ defer conn.Close()
+ tr := &http.Transport{
+ Dial: func(n, addr string) (net.Conn, error) {
+ return conn.Dial(n, addr)
+ },
+ }
+ client := &http.Client{
+ Transport: tr,
+ }
+ resp, err := client.Get(url)
+ if err != nil {
+ t.Fatalf("unable to proxy: %s", err)
+ }
+ // got a body without error
+ t.Log(resp)
+}
diff --git a/ssh/transport.go b/ssh/transport.go
new file mode 100644
index 0000000..e21bc4b
--- /dev/null
+++ b/ssh/transport.go
@@ -0,0 +1,369 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "bufio"
+ "crypto"
+ "crypto/cipher"
+ "crypto/hmac"
+ "crypto/sha1"
+ "crypto/subtle"
+ "errors"
+ "hash"
+ "io"
+ "net"
+ "sync"
+)
+
+const (
+ packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
+ minPacketSize = 16
+ maxPacketSize = 36000
+ minPaddingSize = 4 // TODO(huin) should this be configurable?
+)
+
+// filteredConn reduces the set of methods exposed when embeddeding
+// a net.Conn inside ssh.transport.
+// TODO(dfc) suggestions for a better name will be warmly received.
+type filteredConn interface {
+ // Close closes the connection.
+ Close() error
+
+ // LocalAddr returns the local network address.
+ LocalAddr() net.Addr
+
+ // RemoteAddr returns the remote network address.
+ RemoteAddr() net.Addr
+}
+
+// Types implementing packetWriter provide the ability to send packets to
+// an SSH peer.
+type packetWriter interface {
+ // Encrypt and send a packet of data to the remote peer.
+ writePacket(packet []byte) error
+}
+
+// transport represents the SSH connection to the remote peer.
+type transport struct {
+ reader
+ writer
+
+ filteredConn
+}
+
+// reader represents the incoming connection state.
+type reader struct {
+ io.Reader
+ common
+}
+
+// writer represnts the outgoing connection state.
+type writer struct {
+ *sync.Mutex // protects writer.Writer from concurrent writes
+ *bufio.Writer
+ rand io.Reader
+ common
+}
+
+// common represents the cipher state needed to process messages in a single
+// direction.
+type common struct {
+ seqNum uint32
+ mac hash.Hash
+ cipher cipher.Stream
+
+ cipherAlgo string
+ macAlgo string
+ compressionAlgo string
+}
+
+// Read and decrypt a single packet from the remote peer.
+func (r *reader) readOnePacket() ([]byte, error) {
+ var lengthBytes = make([]byte, 5)
+ var macSize uint32
+ if _, err := io.ReadFull(r, lengthBytes); err != nil {
+ return nil, err
+ }
+
+ r.cipher.XORKeyStream(lengthBytes, lengthBytes)
+
+ if r.mac != nil {
+ r.mac.Reset()
+ seqNumBytes := []byte{
+ byte(r.seqNum >> 24),
+ byte(r.seqNum >> 16),
+ byte(r.seqNum >> 8),
+ byte(r.seqNum),
+ }
+ r.mac.Write(seqNumBytes)
+ r.mac.Write(lengthBytes)
+ macSize = uint32(r.mac.Size())
+ }
+
+ length := uint32(lengthBytes[0])<<24 | uint32(lengthBytes[1])<<16 | uint32(lengthBytes[2])<<8 | uint32(lengthBytes[3])
+ paddingLength := uint32(lengthBytes[4])
+
+ if length <= paddingLength+1 {
+ return nil, errors.New("invalid packet length")
+ }
+ if length > maxPacketSize {
+ return nil, errors.New("packet too large")
+ }
+
+ packet := make([]byte, length-1+macSize)
+ if _, err := io.ReadFull(r, packet); err != nil {
+ return nil, err
+ }
+ mac := packet[length-1:]
+ r.cipher.XORKeyStream(packet, packet[:length-1])
+
+ if r.mac != nil {
+ r.mac.Write(packet[:length-1])
+ if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 {
+ return nil, errors.New("ssh: MAC failure")
+ }
+ }
+
+ r.seqNum++
+ return packet[:length-paddingLength-1], nil
+}
+
+// Read and decrypt next packet discarding debug and noop messages.
+func (t *transport) readPacket() ([]byte, error) {
+ for {
+ packet, err := t.readOnePacket()
+ if err != nil {
+ return nil, err
+ }
+ if packet[0] != msgIgnore && packet[0] != msgDebug {
+ return packet, nil
+ }
+ }
+ panic("unreachable")
+}
+
+// Encrypt and send a packet of data to the remote peer.
+func (w *writer) writePacket(packet []byte) error {
+ w.Mutex.Lock()
+ defer w.Mutex.Unlock()
+
+ paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
+ if paddingLength < 4 {
+ paddingLength += packetSizeMultiple
+ }
+
+ length := len(packet) + 1 + paddingLength
+ lengthBytes := []byte{
+ byte(length >> 24),
+ byte(length >> 16),
+ byte(length >> 8),
+ byte(length),
+ byte(paddingLength),
+ }
+ padding := make([]byte, paddingLength)
+ _, err := io.ReadFull(w.rand, padding)
+ if err != nil {
+ return err
+ }
+
+ if w.mac != nil {
+ w.mac.Reset()
+ seqNumBytes := []byte{
+ byte(w.seqNum >> 24),
+ byte(w.seqNum >> 16),
+ byte(w.seqNum >> 8),
+ byte(w.seqNum),
+ }
+ w.mac.Write(seqNumBytes)
+ w.mac.Write(lengthBytes)
+ w.mac.Write(packet)
+ w.mac.Write(padding)
+ }
+
+ // TODO(dfc) lengthBytes, packet and padding should be
+ // subslices of a single buffer
+ w.cipher.XORKeyStream(lengthBytes, lengthBytes)
+ w.cipher.XORKeyStream(packet, packet)
+ w.cipher.XORKeyStream(padding, padding)
+
+ if _, err := w.Write(lengthBytes); err != nil {
+ return err
+ }
+ if _, err := w.Write(packet); err != nil {
+ return err
+ }
+ if _, err := w.Write(padding); err != nil {
+ return err
+ }
+
+ if w.mac != nil {
+ if _, err := w.Write(w.mac.Sum(nil)); err != nil {
+ return err
+ }
+ }
+
+ if err := w.Flush(); err != nil {
+ return err
+ }
+ w.seqNum++
+ return err
+}
+
+// Send a message to the remote peer
+func (t *transport) sendMessage(typ uint8, msg interface{}) error {
+ packet := marshal(typ, msg)
+ return t.writePacket(packet)
+}
+
+func newTransport(conn net.Conn, rand io.Reader) *transport {
+ return &transport{
+ reader: reader{
+ Reader: bufio.NewReader(conn),
+ common: common{
+ cipher: noneCipher{},
+ },
+ },
+ writer: writer{
+ Writer: bufio.NewWriter(conn),
+ rand: rand,
+ Mutex: new(sync.Mutex),
+ common: common{
+ cipher: noneCipher{},
+ },
+ },
+ filteredConn: conn,
+ }
+}
+
+type direction struct {
+ ivTag []byte
+ keyTag []byte
+ macKeyTag []byte
+}
+
+// TODO(dfc) can this be made a constant ?
+var (
+ serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
+ clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
+)
+
+// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
+// described in RFC 4253, section 6.4. direction should either be serverKeys
+// (to setup server->client keys) or clientKeys (for client->server keys).
+func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error {
+ cipherMode := cipherModes[c.cipherAlgo]
+
+ macKeySize := 20
+
+ iv := make([]byte, cipherMode.ivSize)
+ key := make([]byte, cipherMode.keySize)
+ macKey := make([]byte, macKeySize)
+
+ h := hashFunc.New()
+ generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
+ generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
+ generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
+
+ c.mac = truncatingMAC{12, hmac.New(sha1.New, macKey)}
+
+ cipher, err := cipherMode.createCipher(key, iv)
+ if err != nil {
+ return err
+ }
+
+ c.cipher = cipher
+
+ return nil
+}
+
+// generateKeyMaterial fills out with key material generated from tag, K, H
+// and sessionId, as specified in RFC 4253, section 7.2.
+func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) {
+ var digestsSoFar []byte
+
+ for len(out) > 0 {
+ h.Reset()
+ h.Write(K)
+ h.Write(H)
+
+ if len(digestsSoFar) == 0 {
+ h.Write(tag)
+ h.Write(sessionId)
+ } else {
+ h.Write(digestsSoFar)
+ }
+
+ digest := h.Sum(nil)
+ n := copy(out, digest)
+ out = out[n:]
+ if len(out) > 0 {
+ digestsSoFar = append(digestsSoFar, digest...)
+ }
+ }
+}
+
+// truncatingMAC wraps around a hash.Hash and truncates the output digest to
+// a given size.
+type truncatingMAC struct {
+ length int
+ hmac hash.Hash
+}
+
+func (t truncatingMAC) Write(data []byte) (int, error) {
+ return t.hmac.Write(data)
+}
+
+func (t truncatingMAC) Sum(in []byte) []byte {
+ out := t.hmac.Sum(in)
+ return out[:len(in)+t.length]
+}
+
+func (t truncatingMAC) Reset() {
+ t.hmac.Reset()
+}
+
+func (t truncatingMAC) Size() int {
+ return t.length
+}
+
+func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
+
+// maxVersionStringBytes is the maximum number of bytes that we'll accept as a
+// version string. In the event that the client is talking a different protocol
+// we need to set a limit otherwise we will keep using more and more memory
+// while searching for the end of the version handshake.
+const maxVersionStringBytes = 1024
+
+// Read version string as specified by RFC 4253, section 4.2.
+func readVersion(r io.Reader) ([]byte, error) {
+ versionString := make([]byte, 0, 64)
+ var ok bool
+ var buf [1]byte
+forEachByte:
+ for len(versionString) < maxVersionStringBytes {
+ _, err := io.ReadFull(r, buf[:])
+ if err != nil {
+ return nil, err
+ }
+ // The RFC says that the version should be terminated with \r\n
+ // but several SSH servers actually only send a \n.
+ if buf[0] == '\n' {
+ ok = true
+ break forEachByte
+ }
+ versionString = append(versionString, buf[0])
+ }
+
+ if !ok {
+ return nil, errors.New("ssh: failed to read version string")
+ }
+
+ // There might be a '\r' on the end which we should remove.
+ if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
+ versionString = versionString[:len(versionString)-1]
+ }
+ return versionString, nil
+}
diff --git a/ssh/transport_test.go b/ssh/transport_test.go
new file mode 100644
index 0000000..ab9177f
--- /dev/null
+++ b/ssh/transport_test.go
@@ -0,0 +1,51 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+ "bufio"
+ "bytes"
+ "testing"
+)
+
+func TestReadVersion(t *testing.T) {
+ buf := serverVersion
+ result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
+ if err != nil {
+ t.Errorf("readVersion didn't read version correctly: %s", err)
+ }
+ if !bytes.Equal(buf[:len(buf)-2], result) {
+ t.Error("version read did not match expected")
+ }
+}
+
+func TestReadVersionWithJustLF(t *testing.T) {
+ var buf []byte
+ buf = append(buf, serverVersion...)
+ buf = buf[:len(buf)-1]
+ buf[len(buf)-1] = '\n'
+ result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
+ if err != nil {
+ t.Error("readVersion failed to handle just a \n")
+ }
+ if !bytes.Equal(buf[:len(buf)-1], result) {
+ t.Errorf("version read did not match expected: got %x, want %x", result, buf[:len(buf)-1])
+ }
+}
+
+func TestReadVersionTooLong(t *testing.T) {
+ buf := make([]byte, maxVersionStringBytes+1)
+ if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
+ t.Errorf("readVersion consumed %d bytes without error", len(buf))
+ }
+}
+
+func TestReadVersionWithoutCRLF(t *testing.T) {
+ buf := serverVersion
+ buf = buf[:len(buf)-1]
+ if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
+ t.Error("readVersion did not notice \\n was missing")
+ }
+}
diff --git a/twofish/twofish.go b/twofish/twofish.go
new file mode 100644
index 0000000..0616e7b
--- /dev/null
+++ b/twofish/twofish.go
@@ -0,0 +1,355 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package twofish implements Bruce Schneier's Twofish encryption algorithm.
+package twofish
+
+// Twofish is defined in http://www.schneier.com/paper-twofish-paper.pdf [TWOFISH]
+
+// This code is a port of the LibTom C implementation.
+// See http://libtom.org/?page=features&newsitems=5&whatfile=crypt.
+// LibTomCrypt is free for all purposes under the public domain.
+// It was heavily inspired by the go blowfish package.
+
+import "strconv"
+
+// BlockSize is the constant block size of Twofish.
+const BlockSize = 16
+
+const mdsPolynomial = 0x169 // x^8 + x^6 + x^5 + x^3 + 1, see [TWOFISH] 4.2
+const rsPolynomial = 0x14d // x^8 + x^6 + x^3 + x^2 + 1, see [TWOFISH] 4.3
+
+// A Cipher is an instance of Twofish encryption using a particular key.
+type Cipher struct {
+ s [4][256]uint32
+ k [40]uint32
+}
+
+type KeySizeError int
+
+func (k KeySizeError) Error() string {
+ return "crypto/twofish: invalid key size " + strconv.Itoa(int(k))
+}
+
+// NewCipher creates and returns a Cipher.
+// The key argument should be the Twofish key, 16, 24 or 32 bytes.
+func NewCipher(key []byte) (*Cipher, error) {
+ keylen := len(key)
+
+ if keylen != 16 && keylen != 24 && keylen != 32 {
+ return nil, KeySizeError(keylen)
+ }
+
+ // k is the number of 64 bit words in key
+ k := keylen / 8
+
+ // Create the S[..] words
+ var S [4 * 4]byte
+ for i := 0; i < k; i++ {
+ // Computes [y0 y1 y2 y3] = rs . [x0 x1 x2 x3 x4 x5 x6 x7]
+ for j, rsRow := range rs {
+ for k, rsVal := range rsRow {
+ S[4*i+j] ^= gfMult(key[8*i+k], rsVal, rsPolynomial)
+ }
+ }
+ }
+
+ // Calculate subkeys
+ c := new(Cipher)
+ var tmp [4]byte
+ for i := byte(0); i < 20; i++ {
+ // A = h(p * 2x, Me)
+ for j := range tmp {
+ tmp[j] = 2 * i
+ }
+ A := h(tmp[:], key, 0)
+
+ // B = rolc(h(p * (2x + 1), Mo), 8)
+ for j := range tmp {
+ tmp[j] = 2*i + 1
+ }
+ B := h(tmp[:], key, 1)
+ B = rol(B, 8)
+
+ c.k[2*i] = A + B
+
+ // K[2i+1] = (A + 2B) <<< 9
+ c.k[2*i+1] = rol(2*B+A, 9)
+ }
+
+ // Calculate sboxes
+ switch k {
+ case 2:
+ for i := range c.s[0] {
+ c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][byte(i)]^S[0]]^S[4]], 0)
+ c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][byte(i)]^S[1]]^S[5]], 1)
+ c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][byte(i)]^S[2]]^S[6]], 2)
+ c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][byte(i)]^S[3]]^S[7]], 3)
+ }
+ case 3:
+ for i := range c.s[0] {
+ c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][sbox[1][byte(i)]^S[0]]^S[4]]^S[8]], 0)
+ c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][sbox[1][byte(i)]^S[1]]^S[5]]^S[9]], 1)
+ c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][sbox[0][byte(i)]^S[2]]^S[6]]^S[10]], 2)
+ c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][sbox[0][byte(i)]^S[3]]^S[7]]^S[11]], 3)
+ }
+ default:
+ for i := range c.s[0] {
+ c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][sbox[1][sbox[1][byte(i)]^S[0]]^S[4]]^S[8]]^S[12]], 0)
+ c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][sbox[1][sbox[0][byte(i)]^S[1]]^S[5]]^S[9]]^S[13]], 1)
+ c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][sbox[0][sbox[0][byte(i)]^S[2]]^S[6]]^S[10]]^S[14]], 2)
+ c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][sbox[0][sbox[1][byte(i)]^S[3]]^S[7]]^S[11]]^S[15]], 3)
+ }
+ }
+
+ return c, nil
+}
+
+// Reset zeros the key data, so that it will no longer appear in the process's
+// memory.
+func (c *Cipher) Reset() {
+ for i := range c.k {
+ c.k[i] = 0
+ }
+ for i := range c.s {
+ for j := 0; j < 256; j++ {
+ c.s[i][j] = 0
+ }
+ }
+}
+
+// BlockSize returns the Twofish block size, 16 bytes.
+func (c *Cipher) BlockSize() int { return BlockSize }
+
+// store32l stores src in dst in little-endian form.
+func store32l(dst []byte, src uint32) {
+ dst[0] = byte(src)
+ dst[1] = byte(src >> 8)
+ dst[2] = byte(src >> 16)
+ dst[3] = byte(src >> 24)
+ return
+}
+
+// load32l reads a little-endian uint32 from src.
+func load32l(src []byte) uint32 {
+ return uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24
+}
+
+// rol returns x after a left circular rotation of y bits.
+func rol(x, y uint32) uint32 {
+ return (x << (y & 31)) | (x >> (32 - (y & 31)))
+}
+
+// ror returns x after a right circular rotation of y bits.
+func ror(x, y uint32) uint32 {
+ return (x >> (y & 31)) | (x << (32 - (y & 31)))
+}
+
+// The RS matrix. See [TWOFISH] 4.3
+var rs = [4][8]byte{
+ {0x01, 0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E},
+ {0xA4, 0x56, 0x82, 0xF3, 0x1E, 0xC6, 0x68, 0xE5},
+ {0x02, 0xA1, 0xFC, 0xC1, 0x47, 0xAE, 0x3D, 0x19},
+ {0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E, 0x03},
+}
+
+// sbox tables
+var sbox = [2][256]byte{
+ {
+ 0xa9, 0x67, 0xb3, 0xe8, 0x04, 0xfd, 0xa3, 0x76, 0x9a, 0x92, 0x80, 0x78, 0xe4, 0xdd, 0xd1, 0x38,
+ 0x0d, 0xc6, 0x35, 0x98, 0x18, 0xf7, 0xec, 0x6c, 0x43, 0x75, 0x37, 0x26, 0xfa, 0x13, 0x94, 0x48,
+ 0xf2, 0xd0, 0x8b, 0x30, 0x84, 0x54, 0xdf, 0x23, 0x19, 0x5b, 0x3d, 0x59, 0xf3, 0xae, 0xa2, 0x82,
+ 0x63, 0x01, 0x83, 0x2e, 0xd9, 0x51, 0x9b, 0x7c, 0xa6, 0xeb, 0xa5, 0xbe, 0x16, 0x0c, 0xe3, 0x61,
+ 0xc0, 0x8c, 0x3a, 0xf5, 0x73, 0x2c, 0x25, 0x0b, 0xbb, 0x4e, 0x89, 0x6b, 0x53, 0x6a, 0xb4, 0xf1,
+ 0xe1, 0xe6, 0xbd, 0x45, 0xe2, 0xf4, 0xb6, 0x66, 0xcc, 0x95, 0x03, 0x56, 0xd4, 0x1c, 0x1e, 0xd7,
+ 0xfb, 0xc3, 0x8e, 0xb5, 0xe9, 0xcf, 0xbf, 0xba, 0xea, 0x77, 0x39, 0xaf, 0x33, 0xc9, 0x62, 0x71,
+ 0x81, 0x79, 0x09, 0xad, 0x24, 0xcd, 0xf9, 0xd8, 0xe5, 0xc5, 0xb9, 0x4d, 0x44, 0x08, 0x86, 0xe7,
+ 0xa1, 0x1d, 0xaa, 0xed, 0x06, 0x70, 0xb2, 0xd2, 0x41, 0x7b, 0xa0, 0x11, 0x31, 0xc2, 0x27, 0x90,
+ 0x20, 0xf6, 0x60, 0xff, 0x96, 0x5c, 0xb1, 0xab, 0x9e, 0x9c, 0x52, 0x1b, 0x5f, 0x93, 0x0a, 0xef,
+ 0x91, 0x85, 0x49, 0xee, 0x2d, 0x4f, 0x8f, 0x3b, 0x47, 0x87, 0x6d, 0x46, 0xd6, 0x3e, 0x69, 0x64,
+ 0x2a, 0xce, 0xcb, 0x2f, 0xfc, 0x97, 0x05, 0x7a, 0xac, 0x7f, 0xd5, 0x1a, 0x4b, 0x0e, 0xa7, 0x5a,
+ 0x28, 0x14, 0x3f, 0x29, 0x88, 0x3c, 0x4c, 0x02, 0xb8, 0xda, 0xb0, 0x17, 0x55, 0x1f, 0x8a, 0x7d,
+ 0x57, 0xc7, 0x8d, 0x74, 0xb7, 0xc4, 0x9f, 0x72, 0x7e, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34,
+ 0x6e, 0x50, 0xde, 0x68, 0x65, 0xbc, 0xdb, 0xf8, 0xc8, 0xa8, 0x2b, 0x40, 0xdc, 0xfe, 0x32, 0xa4,
+ 0xca, 0x10, 0x21, 0xf0, 0xd3, 0x5d, 0x0f, 0x00, 0x6f, 0x9d, 0x36, 0x42, 0x4a, 0x5e, 0xc1, 0xe0,
+ },
+ {
+ 0x75, 0xf3, 0xc6, 0xf4, 0xdb, 0x7b, 0xfb, 0xc8, 0x4a, 0xd3, 0xe6, 0x6b, 0x45, 0x7d, 0xe8, 0x4b,
+ 0xd6, 0x32, 0xd8, 0xfd, 0x37, 0x71, 0xf1, 0xe1, 0x30, 0x0f, 0xf8, 0x1b, 0x87, 0xfa, 0x06, 0x3f,
+ 0x5e, 0xba, 0xae, 0x5b, 0x8a, 0x00, 0xbc, 0x9d, 0x6d, 0xc1, 0xb1, 0x0e, 0x80, 0x5d, 0xd2, 0xd5,
+ 0xa0, 0x84, 0x07, 0x14, 0xb5, 0x90, 0x2c, 0xa3, 0xb2, 0x73, 0x4c, 0x54, 0x92, 0x74, 0x36, 0x51,
+ 0x38, 0xb0, 0xbd, 0x5a, 0xfc, 0x60, 0x62, 0x96, 0x6c, 0x42, 0xf7, 0x10, 0x7c, 0x28, 0x27, 0x8c,
+ 0x13, 0x95, 0x9c, 0xc7, 0x24, 0x46, 0x3b, 0x70, 0xca, 0xe3, 0x85, 0xcb, 0x11, 0xd0, 0x93, 0xb8,
+ 0xa6, 0x83, 0x20, 0xff, 0x9f, 0x77, 0xc3, 0xcc, 0x03, 0x6f, 0x08, 0xbf, 0x40, 0xe7, 0x2b, 0xe2,
+ 0x79, 0x0c, 0xaa, 0x82, 0x41, 0x3a, 0xea, 0xb9, 0xe4, 0x9a, 0xa4, 0x97, 0x7e, 0xda, 0x7a, 0x17,
+ 0x66, 0x94, 0xa1, 0x1d, 0x3d, 0xf0, 0xde, 0xb3, 0x0b, 0x72, 0xa7, 0x1c, 0xef, 0xd1, 0x53, 0x3e,
+ 0x8f, 0x33, 0x26, 0x5f, 0xec, 0x76, 0x2a, 0x49, 0x81, 0x88, 0xee, 0x21, 0xc4, 0x1a, 0xeb, 0xd9,
+ 0xc5, 0x39, 0x99, 0xcd, 0xad, 0x31, 0x8b, 0x01, 0x18, 0x23, 0xdd, 0x1f, 0x4e, 0x2d, 0xf9, 0x48,
+ 0x4f, 0xf2, 0x65, 0x8e, 0x78, 0x5c, 0x58, 0x19, 0x8d, 0xe5, 0x98, 0x57, 0x67, 0x7f, 0x05, 0x64,
+ 0xaf, 0x63, 0xb6, 0xfe, 0xf5, 0xb7, 0x3c, 0xa5, 0xce, 0xe9, 0x68, 0x44, 0xe0, 0x4d, 0x43, 0x69,
+ 0x29, 0x2e, 0xac, 0x15, 0x59, 0xa8, 0x0a, 0x9e, 0x6e, 0x47, 0xdf, 0x34, 0x35, 0x6a, 0xcf, 0xdc,
+ 0x22, 0xc9, 0xc0, 0x9b, 0x89, 0xd4, 0xed, 0xab, 0x12, 0xa2, 0x0d, 0x52, 0xbb, 0x02, 0x2f, 0xa9,
+ 0xd7, 0x61, 0x1e, 0xb4, 0x50, 0x04, 0xf6, 0xc2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xbe, 0x91,
+ },
+}
+
+// gfMult returns a·b in GF(2^8)/p
+func gfMult(a, b byte, p uint32) byte {
+ B := [2]uint32{0, uint32(b)}
+ P := [2]uint32{0, p}
+ var result uint32
+
+ // branchless GF multiplier
+ for i := 0; i < 7; i++ {
+ result ^= B[a&1]
+ a >>= 1
+ B[1] = P[B[1]>>7] ^ (B[1] << 1)
+ }
+ result ^= B[a&1]
+ return byte(result)
+}
+
+// mdsColumnMult calculates y{col} where [y0 y1 y2 y3] = MDS · [x0]
+func mdsColumnMult(in byte, col int) uint32 {
+ mul01 := in
+ mul5B := gfMult(in, 0x5B, mdsPolynomial)
+ mulEF := gfMult(in, 0xEF, mdsPolynomial)
+
+ switch col {
+ case 0:
+ return uint32(mul01) | uint32(mul5B)<<8 | uint32(mulEF)<<16 | uint32(mulEF)<<24
+ case 1:
+ return uint32(mulEF) | uint32(mulEF)<<8 | uint32(mul5B)<<16 | uint32(mul01)<<24
+ case 2:
+ return uint32(mul5B) | uint32(mulEF)<<8 | uint32(mul01)<<16 | uint32(mulEF)<<24
+ case 3:
+ return uint32(mul5B) | uint32(mul01)<<8 | uint32(mulEF)<<16 | uint32(mul5B)<<24
+ }
+
+ panic("unreachable")
+}
+
+// h implements the S-box generation function. See [TWOFISH] 4.3.5
+func h(in, key []byte, offset int) uint32 {
+ var y [4]byte
+ for x := range y {
+ y[x] = in[x]
+ }
+ switch len(key) / 8 {
+ case 4:
+ y[0] = sbox[1][y[0]] ^ key[4*(6+offset)+0]
+ y[1] = sbox[0][y[1]] ^ key[4*(6+offset)+1]
+ y[2] = sbox[0][y[2]] ^ key[4*(6+offset)+2]
+ y[3] = sbox[1][y[3]] ^ key[4*(6+offset)+3]
+ fallthrough
+ case 3:
+ y[0] = sbox[1][y[0]] ^ key[4*(4+offset)+0]
+ y[1] = sbox[1][y[1]] ^ key[4*(4+offset)+1]
+ y[2] = sbox[0][y[2]] ^ key[4*(4+offset)+2]
+ y[3] = sbox[0][y[3]] ^ key[4*(4+offset)+3]
+ fallthrough
+ case 2:
+ y[0] = sbox[1][sbox[0][sbox[0][y[0]]^key[4*(2+offset)+0]]^key[4*(0+offset)+0]]
+ y[1] = sbox[0][sbox[0][sbox[1][y[1]]^key[4*(2+offset)+1]]^key[4*(0+offset)+1]]
+ y[2] = sbox[1][sbox[1][sbox[0][y[2]]^key[4*(2+offset)+2]]^key[4*(0+offset)+2]]
+ y[3] = sbox[0][sbox[1][sbox[1][y[3]]^key[4*(2+offset)+3]]^key[4*(0+offset)+3]]
+ }
+ // [y0 y1 y2 y3] = MDS . [x0 x1 x2 x3]
+ var mdsMult uint32
+ for i := range y {
+ mdsMult ^= mdsColumnMult(y[i], i)
+ }
+ return mdsMult
+}
+
+// Encrypt encrypts a 16-byte block from src to dst, which may overlap.
+// Note that for amounts of data larger than a block,
+// it is not safe to just call Encrypt on successive blocks;
+// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
+func (c *Cipher) Encrypt(dst, src []byte) {
+ S1 := c.s[0]
+ S2 := c.s[1]
+ S3 := c.s[2]
+ S4 := c.s[3]
+
+ // Load input
+ ia := load32l(src[0:4])
+ ib := load32l(src[4:8])
+ ic := load32l(src[8:12])
+ id := load32l(src[12:16])
+
+ // Pre-whitening
+ ia ^= c.k[0]
+ ib ^= c.k[1]
+ ic ^= c.k[2]
+ id ^= c.k[3]
+
+ for i := 0; i < 8; i++ {
+ k := c.k[8+i*4 : 12+i*4]
+ t2 := S2[byte(ib)] ^ S3[byte(ib>>8)] ^ S4[byte(ib>>16)] ^ S1[byte(ib>>24)]
+ t1 := S1[byte(ia)] ^ S2[byte(ia>>8)] ^ S3[byte(ia>>16)] ^ S4[byte(ia>>24)] + t2
+ ic = ror(ic^(t1+k[0]), 1)
+ id = rol(id, 1) ^ (t2 + t1 + k[1])
+
+ t2 = S2[byte(id)] ^ S3[byte(id>>8)] ^ S4[byte(id>>16)] ^ S1[byte(id>>24)]
+ t1 = S1[byte(ic)] ^ S2[byte(ic>>8)] ^ S3[byte(ic>>16)] ^ S4[byte(ic>>24)] + t2
+ ia = ror(ia^(t1+k[2]), 1)
+ ib = rol(ib, 1) ^ (t2 + t1 + k[3])
+ }
+
+ // Output with "undo last swap"
+ ta := ic ^ c.k[4]
+ tb := id ^ c.k[5]
+ tc := ia ^ c.k[6]
+ td := ib ^ c.k[7]
+
+ store32l(dst[0:4], ta)
+ store32l(dst[4:8], tb)
+ store32l(dst[8:12], tc)
+ store32l(dst[12:16], td)
+}
+
+// Decrypt decrypts a 16-byte block from src to dst, which may overlap.
+func (c *Cipher) Decrypt(dst, src []byte) {
+ S1 := c.s[0]
+ S2 := c.s[1]
+ S3 := c.s[2]
+ S4 := c.s[3]
+
+ // Load input
+ ta := load32l(src[0:4])
+ tb := load32l(src[4:8])
+ tc := load32l(src[8:12])
+ td := load32l(src[12:16])
+
+ // Undo undo final swap
+ ia := tc ^ c.k[6]
+ ib := td ^ c.k[7]
+ ic := ta ^ c.k[4]
+ id := tb ^ c.k[5]
+
+ for i := 8; i > 0; i-- {
+ k := c.k[4+i*4 : 8+i*4]
+ t2 := S2[byte(id)] ^ S3[byte(id>>8)] ^ S4[byte(id>>16)] ^ S1[byte(id>>24)]
+ t1 := S1[byte(ic)] ^ S2[byte(ic>>8)] ^ S3[byte(ic>>16)] ^ S4[byte(ic>>24)] + t2
+ ia = rol(ia, 1) ^ (t1 + k[2])
+ ib = ror(ib^(t2+t1+k[3]), 1)
+
+ t2 = S2[byte(ib)] ^ S3[byte(ib>>8)] ^ S4[byte(ib>>16)] ^ S1[byte(ib>>24)]
+ t1 = S1[byte(ia)] ^ S2[byte(ia>>8)] ^ S3[byte(ia>>16)] ^ S4[byte(ia>>24)] + t2
+ ic = rol(ic, 1) ^ (t1 + k[0])
+ id = ror(id^(t2+t1+k[1]), 1)
+ }
+
+ // Undo pre-whitening
+ ia ^= c.k[0]
+ ib ^= c.k[1]
+ ic ^= c.k[2]
+ id ^= c.k[3]
+
+ store32l(dst[0:4], ia)
+ store32l(dst[4:8], ib)
+ store32l(dst[8:12], ic)
+ store32l(dst[12:16], id)
+}
diff --git a/twofish/twofish_test.go b/twofish/twofish_test.go
new file mode 100644
index 0000000..303081f
--- /dev/null
+++ b/twofish/twofish_test.go
@@ -0,0 +1,129 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package twofish
+
+import (
+ "bytes"
+ "testing"
+)
+
+var qbox = [2][4][16]byte{
+ {
+ {0x8, 0x1, 0x7, 0xD, 0x6, 0xF, 0x3, 0x2, 0x0, 0xB, 0x5, 0x9, 0xE, 0xC, 0xA, 0x4},
+ {0xE, 0xC, 0xB, 0x8, 0x1, 0x2, 0x3, 0x5, 0xF, 0x4, 0xA, 0x6, 0x7, 0x0, 0x9, 0xD},
+ {0xB, 0xA, 0x5, 0xE, 0x6, 0xD, 0x9, 0x0, 0xC, 0x8, 0xF, 0x3, 0x2, 0x4, 0x7, 0x1},
+ {0xD, 0x7, 0xF, 0x4, 0x1, 0x2, 0x6, 0xE, 0x9, 0xB, 0x3, 0x0, 0x8, 0x5, 0xC, 0xA},
+ },
+ {
+ {0x2, 0x8, 0xB, 0xD, 0xF, 0x7, 0x6, 0xE, 0x3, 0x1, 0x9, 0x4, 0x0, 0xA, 0xC, 0x5},
+ {0x1, 0xE, 0x2, 0xB, 0x4, 0xC, 0x3, 0x7, 0x6, 0xD, 0xA, 0x5, 0xF, 0x9, 0x0, 0x8},
+ {0x4, 0xC, 0x7, 0x5, 0x1, 0x6, 0x9, 0xA, 0x0, 0xE, 0xD, 0x8, 0x2, 0xB, 0x3, 0xF},
+ {0xB, 0x9, 0x5, 0x1, 0xC, 0x3, 0xD, 0xE, 0x6, 0x4, 0x7, 0xF, 0x2, 0x0, 0x8, 0xA},
+ },
+}
+
+// genSbox generates the variable sbox
+func genSbox(qi int, x byte) byte {
+ a0, b0 := x/16, x%16
+ for i := 0; i < 2; i++ {
+ a1 := a0 ^ b0
+ b1 := (a0 ^ ((b0 << 3) | (b0 >> 1)) ^ (a0 << 3)) & 15
+ a0 = qbox[qi][2*i][a1]
+ b0 = qbox[qi][2*i+1][b1]
+ }
+ return (b0 << 4) + a0
+}
+
+func TestSbox(t *testing.T) {
+ for n := range sbox {
+ for m := range sbox[n] {
+ if genSbox(n, byte(m)) != sbox[n][m] {
+ t.Errorf("#%d|%d: sbox value = %d want %d", n, m, sbox[n][m], genSbox(n, byte(m)))
+ }
+ }
+ }
+}
+
+var testVectors = []struct {
+ key []byte
+ dec []byte
+ enc []byte
+}{
+ // These tests are extracted from LibTom
+ {
+ []byte{0x9F, 0x58, 0x9F, 0x5C, 0xF6, 0x12, 0x2C, 0x32, 0xB6, 0xBF, 0xEC, 0x2F, 0x2A, 0xE8, 0xC3, 0x5A},
+ []byte{0xD4, 0x91, 0xDB, 0x16, 0xE7, 0xB1, 0xC3, 0x9E, 0x86, 0xCB, 0x08, 0x6B, 0x78, 0x9F, 0x54, 0x19},
+ []byte{0x01, 0x9F, 0x98, 0x09, 0xDE, 0x17, 0x11, 0x85, 0x8F, 0xAA, 0xC3, 0xA3, 0xBA, 0x20, 0xFB, 0xC3},
+ },
+ {
+ []byte{0x88, 0xB2, 0xB2, 0x70, 0x6B, 0x10, 0x5E, 0x36, 0xB4, 0x46, 0xBB, 0x6D, 0x73, 0x1A, 0x1E, 0x88,
+ 0xEF, 0xA7, 0x1F, 0x78, 0x89, 0x65, 0xBD, 0x44},
+ []byte{0x39, 0xDA, 0x69, 0xD6, 0xBA, 0x49, 0x97, 0xD5, 0x85, 0xB6, 0xDC, 0x07, 0x3C, 0xA3, 0x41, 0xB2},
+ []byte{0x18, 0x2B, 0x02, 0xD8, 0x14, 0x97, 0xEA, 0x45, 0xF9, 0xDA, 0xAC, 0xDC, 0x29, 0x19, 0x3A, 0x65},
+ },
+ {
+ []byte{0xD4, 0x3B, 0xB7, 0x55, 0x6E, 0xA3, 0x2E, 0x46, 0xF2, 0xA2, 0x82, 0xB7, 0xD4, 0x5B, 0x4E, 0x0D,
+ 0x57, 0xFF, 0x73, 0x9D, 0x4D, 0xC9, 0x2C, 0x1B, 0xD7, 0xFC, 0x01, 0x70, 0x0C, 0xC8, 0x21, 0x6F},
+ []byte{0x90, 0xAF, 0xE9, 0x1B, 0xB2, 0x88, 0x54, 0x4F, 0x2C, 0x32, 0xDC, 0x23, 0x9B, 0x26, 0x35, 0xE6},
+ []byte{0x6C, 0xB4, 0x56, 0x1C, 0x40, 0xBF, 0x0A, 0x97, 0x05, 0x93, 0x1C, 0xB6, 0xD4, 0x08, 0xE7, 0xFA},
+ },
+ // These test are derived from http://www.schneier.com/code/ecb_ival.txt
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x9F, 0x58, 0x9F, 0x5C, 0xF6, 0x12, 0x2C, 0x32, 0xB6, 0xBF, 0xEC, 0x2F, 0x2A, 0xE8, 0xC3, 0x5A},
+ },
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ },
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xCF, 0xD1, 0xD2, 0xE5, 0xA9, 0xBE, 0x9C, 0xDF, 0x50, 0x1F, 0x13, 0xB8, 0x92, 0xBD, 0x22, 0x48},
+ },
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+ },
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x37, 0x52, 0x7B, 0xE0, 0x05, 0x23, 0x34, 0xB8, 0x9F, 0x0C, 0xFC, 0xCA, 0xE8, 0x7C, 0xFA, 0x20},
+ },
+}
+
+func TestCipher(t *testing.T) {
+ for n, tt := range testVectors {
+ // Test if the plaintext (dec) is encrypts to the given
+ // ciphertext (enc) using the given key. Test also if enc can
+ // be decrypted again into dec.
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("#%d: NewCipher: %v", n, err)
+ return
+ }
+
+ buf := make([]byte, 16)
+ c.Encrypt(buf, tt.dec)
+ if !bytes.Equal(buf, tt.enc) {
+ t.Errorf("#%d: encrypt = %x want %x", n, buf, tt.enc)
+ }
+ c.Decrypt(buf, tt.enc)
+ if !bytes.Equal(buf, tt.dec) {
+ t.Errorf("#%d: decrypt = %x want %x", n, buf, tt.dec)
+ }
+
+ // Test that 16 zero bytes, encrypted 1000 times then decrypted
+ // 1000 times results in zero bytes again.
+ zero := make([]byte, 16)
+ buf = make([]byte, 16)
+ for i := 0; i < 1000; i++ {
+ c.Encrypt(buf, buf)
+ }
+ for i := 0; i < 1000; i++ {
+ c.Decrypt(buf, buf)
+ }
+ if !bytes.Equal(buf, zero) {
+ t.Errorf("#%d: encrypt/decrypt 1000: have %x want %x", n, buf, zero)
+ }
+ }
+}
diff --git a/xtea/block.go b/xtea/block.go
new file mode 100644
index 0000000..bf5d245
--- /dev/null
+++ b/xtea/block.go
@@ -0,0 +1,66 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+ Implementation adapted from Needham and Wheeler's paper:
+ http://www.cix.co.uk/~klockstone/xtea.pdf
+
+ A precalculated look up table is used during encryption/decryption for values that are based purely on the key.
+*/
+
+package xtea
+
+// XTEA is based on 64 rounds.
+const numRounds = 64
+
+// blockToUint32 reads an 8 byte slice into two uint32s.
+// The block is treated as big endian.
+func blockToUint32(src []byte) (uint32, uint32) {
+ r0 := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+ r1 := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+ return r0, r1
+}
+
+// uint32ToBlock writes two uint32s into an 8 byte data block.
+// Values are written as big endian.
+func uint32ToBlock(v0, v1 uint32, dst []byte) {
+ dst[0] = byte(v0 >> 24)
+ dst[1] = byte(v0 >> 16)
+ dst[2] = byte(v0 >> 8)
+ dst[3] = byte(v0)
+ dst[4] = byte(v1 >> 24)
+ dst[5] = byte(v1 >> 16)
+ dst[6] = byte(v1 >> 8)
+ dst[7] = byte(v1 >> 0)
+}
+
+// encryptBlock encrypts a single 8 byte block using XTEA.
+func encryptBlock(c *Cipher, dst, src []byte) {
+ v0, v1 := blockToUint32(src)
+
+ // Two rounds of XTEA applied per loop
+ for i := 0; i < numRounds; {
+ v0 += ((v1<<4 ^ v1>>5) + v1) ^ c.table[i]
+ i++
+ v1 += ((v0<<4 ^ v0>>5) + v0) ^ c.table[i]
+ i++
+ }
+
+ uint32ToBlock(v0, v1, dst)
+}
+
+// decryptBlock decrypt a single 8 byte block using XTEA.
+func decryptBlock(c *Cipher, dst, src []byte) {
+ v0, v1 := blockToUint32(src)
+
+ // Two rounds of XTEA applied per loop
+ for i := numRounds; i > 0; {
+ i--
+ v1 -= ((v0<<4 ^ v0>>5) + v0) ^ c.table[i]
+ i--
+ v0 -= ((v1<<4 ^ v1>>5) + v1) ^ c.table[i]
+ }
+
+ uint32ToBlock(v0, v1, dst)
+}
diff --git a/xtea/cipher.go b/xtea/cipher.go
new file mode 100644
index 0000000..3ed0581
--- /dev/null
+++ b/xtea/cipher.go
@@ -0,0 +1,89 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package xtea implements XTEA encryption, as defined in Needham and Wheeler's
+// 1997 technical report, "Tea extensions."
+package xtea
+
+// For details, see http://www.cix.co.uk/~klockstone/xtea.pdf
+
+import "strconv"
+
+// The XTEA block size in bytes.
+const BlockSize = 8
+
+// A Cipher is an instance of an XTEA cipher using a particular key.
+// table contains a series of precalculated values that are used each round.
+type Cipher struct {
+ table [64]uint32
+}
+
+type KeySizeError int
+
+func (k KeySizeError) Error() string {
+ return "crypto/xtea: invalid key size " + strconv.Itoa(int(k))
+}
+
+// NewCipher creates and returns a new Cipher.
+// The key argument should be the XTEA key.
+// XTEA only supports 128 bit (16 byte) keys.
+func NewCipher(key []byte) (*Cipher, error) {
+ k := len(key)
+ switch k {
+ default:
+ return nil, KeySizeError(k)
+ case 16:
+ break
+ }
+
+ c := new(Cipher)
+ initCipher(c, key)
+
+ return c, nil
+}
+
+// BlockSize returns the XTEA block size, 8 bytes.
+// It is necessary to satisfy the Block interface in the
+// package "crypto/cipher".
+func (c *Cipher) BlockSize() int { return BlockSize }
+
+// Encrypt encrypts the 8 byte buffer src using the key and stores the result in dst.
+// Note that for amounts of data larger than a block,
+// it is not safe to just call Encrypt on successive blocks;
+// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
+func (c *Cipher) Encrypt(dst, src []byte) { encryptBlock(c, dst, src) }
+
+// Decrypt decrypts the 8 byte buffer src using the key k and stores the result in dst.
+func (c *Cipher) Decrypt(dst, src []byte) { decryptBlock(c, dst, src) }
+
+// Reset zeros the table, so that it will no longer appear in the process's memory.
+func (c *Cipher) Reset() {
+ for i := 0; i < len(c.table); i++ {
+ c.table[i] = 0
+ }
+}
+
+// initCipher initializes the cipher context by creating a look up table
+// of precalculated values that are based on the key.
+func initCipher(c *Cipher, key []byte) {
+ // Load the key into four uint32s
+ var k [4]uint32
+ for i := 0; i < len(k); i++ {
+ j := i << 2 // Multiply by 4
+ k[i] = uint32(key[j+0])<<24 | uint32(key[j+1])<<16 | uint32(key[j+2])<<8 | uint32(key[j+3])
+ }
+
+ // Precalculate the table
+ const delta = 0x9E3779B9
+ var sum uint32 = 0
+
+ // Two rounds of XTEA applied per loop
+ for i := 0; i < numRounds; {
+ c.table[i] = sum + k[sum&3]
+ i++
+ sum += delta
+ c.table[i] = sum + k[(sum>>11)&3]
+ i++
+ }
+}
diff --git a/xtea/xtea_test.go b/xtea/xtea_test.go
new file mode 100644
index 0000000..217d96a
--- /dev/null
+++ b/xtea/xtea_test.go
@@ -0,0 +1,246 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xtea
+
+import (
+ "testing"
+)
+
+// A sample test key for when we just want to initialize a cipher
+var testKey = []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}
+
+// Test that the block size for XTEA is correct
+func TestBlocksize(t *testing.T) {
+ if BlockSize != 8 {
+ t.Errorf("BlockSize constant - expected 8, got %d", BlockSize)
+ return
+ }
+
+ c, err := NewCipher(testKey)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+ return
+ }
+
+ result := c.BlockSize()
+ if result != 8 {
+ t.Errorf("BlockSize function - expected 8, got %d", result)
+ return
+ }
+}
+
+// A series of test values to confirm that the Cipher.table array was initialized correctly
+var testTable = []uint32{
+ 0x00112233, 0x6B1568B8, 0xE28CE030, 0xC5089E2D, 0xC5089E2D, 0x1EFBD3A2, 0xA7845C2A, 0x78EF0917,
+ 0x78EF0917, 0x172682D0, 0x5B6AC714, 0x822AC955, 0x3DE68511, 0xDC1DFECA, 0x2062430E, 0x3611343F,
+ 0xF1CCEFFB, 0x900469B4, 0xD448ADF8, 0x2E3BE36D, 0xB6C46BF5, 0x994029F2, 0x994029F2, 0xF3335F67,
+ 0x6AAAD6DF, 0x4D2694DC, 0x4D2694DC, 0xEB5E0E95, 0x2FA252D9, 0x4551440A, 0x121E10D6, 0xB0558A8F,
+ 0xE388BDC3, 0x0A48C004, 0xC6047BC0, 0x643BF579, 0xA88039BD, 0x02736F32, 0x8AFBF7BA, 0x5C66A4A7,
+ 0x5C66A4A7, 0xC76AEB2C, 0x3EE262A4, 0x215E20A1, 0x215E20A1, 0x7B515616, 0x03D9DE9E, 0x1988CFCF,
+ 0xD5448B8B, 0x737C0544, 0xB7C04988, 0xDE804BC9, 0x9A3C0785, 0x3873813E, 0x7CB7C582, 0xD6AAFAF7,
+ 0x4E22726F, 0x309E306C, 0x309E306C, 0x8A9165E1, 0x1319EE69, 0xF595AC66, 0xF595AC66, 0x4F88E1DB,
+}
+
+// Test that the cipher context is initialized correctly
+func TestCipherInit(t *testing.T) {
+ c, err := NewCipher(testKey)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+ return
+ }
+
+ for i := 0; i < len(c.table); i++ {
+ if c.table[i] != testTable[i] {
+ t.Errorf("NewCipher() failed to initialize Cipher.table[%d] correctly. Expected %08X, got %08X", i, testTable[i], c.table[i])
+ break
+ }
+ }
+}
+
+// Test that invalid key sizes return an error
+func TestInvalidKeySize(t *testing.T) {
+ // Test a long key
+ key := []byte{
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+ }
+
+ _, err := NewCipher(key)
+ if err == nil {
+ t.Errorf("Invalid key size %d didn't result in an error.", len(key))
+ }
+
+ // Test a short key
+ key = []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}
+
+ _, err = NewCipher(key)
+ if err == nil {
+ t.Errorf("Invalid key size %d didn't result in an error.", len(key))
+ }
+}
+
+// Test that we can correctly decode some bytes we have encoded
+func TestEncodeDecode(t *testing.T) {
+ original := []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}
+ input := original
+ output := make([]byte, BlockSize)
+
+ c, err := NewCipher(testKey)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+ return
+ }
+
+ // Encrypt the input block
+ c.Encrypt(output, input)
+
+ // Check that the output does not match the input
+ differs := false
+ for i := 0; i < len(input); i++ {
+ if output[i] != input[i] {
+ differs = true
+ break
+ }
+ }
+ if differs == false {
+ t.Error("Cipher.Encrypt: Failed to encrypt the input block.")
+ return
+ }
+
+ // Decrypt the block we just encrypted
+ input = output
+ output = make([]byte, BlockSize)
+ c.Decrypt(output, input)
+
+ // Check that the output from decrypt matches our initial input
+ for i := 0; i < len(input); i++ {
+ if output[i] != original[i] {
+ t.Errorf("Decrypted byte %d differed. Expected %02X, got %02X\n", i, original[i], output[i])
+ return
+ }
+ }
+}
+
+// Test Vectors
+type CryptTest struct {
+ key []byte
+ plainText []byte
+ cipherText []byte
+}
+
+var CryptTests = []CryptTest{
+ // These were sourced from http://www.freemedialibrary.com/index.php/XTEA_test_vectors
+ {
+ []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
+ []byte{0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48},
+ []byte{0x49, 0x7d, 0xf3, 0xd0, 0x72, 0x61, 0x2c, 0xb5},
+ },
+ {
+ []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
+ []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+ []byte{0xe7, 0x8f, 0x2d, 0x13, 0x74, 0x43, 0x41, 0xd8},
+ },
+ {
+ []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
+ []byte{0x5a, 0x5b, 0x6e, 0x27, 0x89, 0x48, 0xd7, 0x7f},
+ []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+ },
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48},
+ []byte{0xa0, 0x39, 0x05, 0x89, 0xf8, 0xb8, 0xef, 0xa5},
+ },
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+ []byte{0xed, 0x23, 0x37, 0x5a, 0x82, 0x1a, 0x8c, 0x2d},
+ },
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x70, 0xe1, 0x22, 0x5d, 0x6e, 0x4e, 0x76, 0x55},
+ []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+ },
+
+ // These vectors are from http://wiki.secondlife.com/wiki/XTEA_Strong_Encryption_Implementation#Bouncy_Castle_C.23_API
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xDE, 0xE9, 0xD4, 0xD8, 0xF7, 0x13, 0x1E, 0xD9},
+ },
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
+ []byte{0x06, 0x5C, 0x1B, 0x89, 0x75, 0xC6, 0xA8, 0x16},
+ },
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x12, 0x34, 0x56, 0x78, 0x23, 0x45, 0x67, 0x89, 0x34, 0x56, 0x78, 0x9A},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x1F, 0xF9, 0xA0, 0x26, 0x1A, 0xC6, 0x42, 0x64},
+ },
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x12, 0x34, 0x56, 0x78, 0x23, 0x45, 0x67, 0x89, 0x34, 0x56, 0x78, 0x9A},
+ []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
+ []byte{0x8C, 0x67, 0x15, 0x5B, 0x2E, 0xF9, 0x1E, 0xAD},
+ },
+}
+
+// Test encryption
+func TestCipherEncrypt(t *testing.T) {
+ for i, tt := range CryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes), vector %d = %s", len(tt.key), i, err)
+ continue
+ }
+
+ out := make([]byte, len(tt.plainText))
+ c.Encrypt(out, tt.plainText)
+
+ for j := 0; j < len(out); j++ {
+ if out[j] != tt.cipherText[j] {
+ t.Errorf("Cipher.Encrypt %d: out[%d] = %02X, expected %02X", i, j, out[j], tt.cipherText[j])
+ break
+ }
+ }
+ }
+}
+
+// Test decryption
+func TestCipherDecrypt(t *testing.T) {
+ for i, tt := range CryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes), vector %d = %s", len(tt.key), i, err)
+ continue
+ }
+
+ out := make([]byte, len(tt.cipherText))
+ c.Decrypt(out, tt.cipherText)
+
+ for j := 0; j < len(out); j++ {
+ if out[j] != tt.plainText[j] {
+ t.Errorf("Cipher.Decrypt %d: out[%d] = %02X, expected %02X", i, j, out[j], tt.plainText[j])
+ break
+ }
+ }
+ }
+}
+
+// Test resetting the cipher context
+func TestReset(t *testing.T) {
+ c, err := NewCipher(testKey)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+ return
+ }
+
+ c.Reset()
+ for i := 0; i < len(c.table); i++ {
+ if c.table[i] != 0 {
+ t.Errorf("Cipher.Reset: Failed to clear Cipher.table[%d]. expected 0, got %08X", i, c.table[i])
+ return
+ }
+ }
+}