Add token generation using go macaroon (#437)

* Add Go macaroon library

Signed-off-by: Anant Prakash <anantprakashjsr@gmail.com>

* Add macaroon generation and serialization, for login token.

Signed-off-by: Anant Prakash <anantprakashjsr@gmail.com>

* Remove copyright, trim empty lines

* Make Serialize functions private

* Fix typos
This commit is contained in:
Anant Prakash 2018-05-22 14:43:58 +05:30 committed by Andrew Morgan
parent 89e0a9e812
commit afeab7b2d4
37 changed files with 6295 additions and 0 deletions

View file

@ -0,0 +1,132 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tokens
import (
"encoding/base64"
"errors"
"fmt"
"strconv"
"time"
"github.com/go-macaroon/macaroon"
)
const (
macaroonVersion = macaroon.V2
defaultDuration = 2 * 60
// UserPrefix is a common prefix for every user_id caveat
UserPrefix = "user_id = "
// TimePrefix is a common prefix for every expiry caveat
TimePrefix = "time < "
// Gen is a common caveat for every token
Gen = "gen = 1"
)
// TokenOptions represent parameters of Token
type TokenOptions struct {
ServerPrivateKey []byte `yaml:"private_key"`
ServerName string `yaml:"server_name"`
UserID string `json:"user_id"`
Duration int // optional
}
// GenerateLoginToken generates a short term login token to be used as
// token authentication ("m.login.token")
func GenerateLoginToken(op TokenOptions) (string, error) {
if !isValidTokenOptions(op) {
return "", errors.New("The given TokenOptions is invalid")
}
mac, err := generateBaseMacaroon(op.ServerPrivateKey, op.ServerName, op.UserID)
if err != nil {
return "", err
}
if op.Duration == 0 {
op.Duration = defaultDuration
}
now := time.Now().Second()
expiryCaveat := TimePrefix + strconv.Itoa(now+op.Duration)
err = mac.AddFirstPartyCaveat([]byte(expiryCaveat))
if err != nil {
return "", macaroonError(err)
}
urlSafeEncode, err := serializeMacaroon(*mac)
if err != nil {
return "", macaroonError(err)
}
return urlSafeEncode, nil
}
// isValidTokenOptions checks for required fields in a TokenOptions
func isValidTokenOptions(op TokenOptions) bool {
if op.ServerPrivateKey == nil || op.ServerName == "" || op.UserID == "" {
return false
}
return true
}
// generateBaseMacaroon generates a base macaroon common for accessToken & loginToken.
// Returns a macaroon tied with userID,
// returns an error if something goes wrong.
func generateBaseMacaroon(
secret []byte, ServerName string, userID string,
) (*macaroon.Macaroon, error) {
mac, err := macaroon.New(secret, []byte(userID), ServerName, macaroonVersion)
if err != nil {
return nil, macaroonError(err)
}
err = mac.AddFirstPartyCaveat([]byte(Gen))
if err != nil {
return nil, macaroonError(err)
}
err = mac.AddFirstPartyCaveat([]byte(UserPrefix + userID))
if err != nil {
return nil, macaroonError(err)
}
return mac, nil
}
func macaroonError(err error) error {
return fmt.Errorf("Macaroon creation failed: %s", err.Error())
}
// serializeMacaroon takes a macaroon to be serialized.
// returns its base64 encoded string, URL safe, which can be sent via web, email, etc.
func serializeMacaroon(m macaroon.Macaroon) (string, error) {
bin, err := m.MarshalBinary()
if err != nil {
return "", err
}
urlSafeEncode := base64.RawURLEncoding.EncodeToString(bin)
return urlSafeEncode, nil
}
// deSerializeMacaroon takes a base64 encoded string of a macaroon to be de-serialized.
// Returns a macaroon. On failure returns error with description.
func deSerializeMacaroon(urlSafeEncode string) (macaroon.Macaroon, error) {
var mac macaroon.Macaroon
bin, err := base64.RawURLEncoding.DecodeString(urlSafeEncode)
if err != nil {
return mac, err
}
err = mac.UnmarshalBinary(bin)
return mac, err
}

View file

@ -0,0 +1,80 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tokens
import (
"testing"
)
var (
validTokenOp = TokenOptions{
ServerPrivateKey: []byte("aSecretKey"),
ServerName: "aRandomServerName",
UserID: "aRandomUserID",
}
invalidTokenOps = map[string]TokenOptions{
"ServerPrivateKey": {
ServerName: "aRandomServerName",
UserID: "aRandomUserID",
},
"ServerName": {
ServerPrivateKey: []byte("aSecretKey"),
UserID: "aRandomUserID",
},
"UserID": {
ServerPrivateKey: []byte("aSecretKey"),
ServerName: "aRandomServerName",
},
}
)
func TestGenerateLoginToken(t *testing.T) {
// Test valid
_, err := GenerateLoginToken(validTokenOp)
if err != nil {
t.Errorf("Token generation failed for valid TokenOptions with err: %s", err.Error())
}
// Test invalids
for missing, invalidTokenOp := range invalidTokenOps {
_, err := GenerateLoginToken(invalidTokenOp)
if err == nil {
t.Errorf("Token generation should fail for TokenOptions with missing %s", missing)
}
}
}
func serializationTestError(err error) string {
return "Token Serialization test failed with err: " + err.Error()
}
func TestSerialization(t *testing.T) {
fakeToken, err := GenerateLoginToken(validTokenOp)
if err != nil {
t.Errorf(serializationTestError(err))
}
fakeMacaroon, err := deSerializeMacaroon(fakeToken)
if err != nil {
t.Errorf(serializationTestError(err))
}
sameFakeToken, err := serializeMacaroon(fakeMacaroon)
if err != nil {
t.Errorf(serializationTestError(err))
}
if sameFakeToken != fakeToken {
t.Errorf("Token Serialization mismatch")
}
}

27
vendor/manifest vendored
View file

@ -77,6 +77,12 @@
"revision": "44cc805cf13205b55f69e14bcb69867d1ae92f98",
"branch": "master"
},
{
"importpath": "github.com/go-macaroon/macaroon",
"repository": "https://github.com/go-macaroon/macaroon",
"revision": "bed2a428da6e56d950bed5b41fcbae3141e5b0d0",
"branch": "HEAD"
},
{
"importpath": "github.com/golang/protobuf/proto",
"repository": "https://github.com/golang/protobuf",
@ -369,6 +375,27 @@
"branch": "master",
"path": "/ed25519"
},
{
"importpath": "golang.org/x/crypto/nacl/secretbox",
"repository": "https://go.googlesource.com/crypto",
"revision": "d6449816ce06963d9d136eee5a56fca5b0616e7e",
"branch": "master",
"path": "/nacl/secretbox"
},
{
"importpath": "golang.org/x/crypto/poly1305",
"repository": "https://go.googlesource.com/crypto",
"revision": "d6449816ce06963d9d136eee5a56fca5b0616e7e",
"branch": "master",
"path": "/poly1305"
},
{
"importpath": "golang.org/x/crypto/salsa20/salsa",
"repository": "https://go.googlesource.com/crypto",
"revision": "d6449816ce06963d9d136eee5a56fca5b0616e7e",
"branch": "master",
"path": "/salsa20/salsa"
},
{
"importpath": "golang.org/x/crypto/ssh",
"repository": "https://go.googlesource.com/crypto",

View file

@ -0,0 +1,26 @@
Copyright © 2014, Roger Peppe
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of this project nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,159 @@
# macaroon
--
import "gopkg.in/macaroon.v1"
The macaroon package implements macaroons as described in the paper "Macaroons:
Cookies with Contextual Caveats for Decentralized Authorization in the Cloud"
(http://theory.stanford.edu/~ataly/Papers/macaroons.pdf)
See the macaroon bakery packages at http://godoc.org/gopkg.in/macaroon-bakery.v0
for higher level services and operations that use macaroons.
## Usage
#### type Caveat
```go
type Caveat struct {
Id string
Location string
}
```
#### type Macaroon
```go
type Macaroon struct {
}
```
Macaroon holds a macaroon. See Fig. 7 of
http://theory.stanford.edu/~ataly/Papers/macaroons.pdf for a description of the
data contained within. Macaroons are mutable objects - use Clone as appropriate
to avoid unwanted mutation.
#### func New
```go
func New(rootKey []byte, id, loc string) (*Macaroon, error)
```
New returns a new macaroon with the given root key, identifier and location.
#### func (*Macaroon) AddFirstPartyCaveat
```go
func (m *Macaroon) AddFirstPartyCaveat(caveatId string) error
```
AddFirstPartyCaveat adds a caveat that will be verified by the target service.
#### func (*Macaroon) AddThirdPartyCaveat
```go
func (m *Macaroon) AddThirdPartyCaveat(rootKey []byte, caveatId string, loc string) error
```
AddThirdPartyCaveat adds a third-party caveat to the macaroon, using the given
shared root key, caveat id and location hint. The caveat id should encode the
root key in some way, either by encrypting it with a key known to the third
party or by holding a reference to it stored in the third party's storage.
#### func (*Macaroon) Bind
```go
func (m *Macaroon) Bind(sig []byte)
```
Bind prepares the macaroon for being used to discharge the macaroon with the
given signature sig. This must be used before it is used in the discharges
argument to Verify.
#### func (*Macaroon) Caveats
```go
func (m *Macaroon) Caveats() []Caveat
```
Caveats returns the macaroon's caveats. This method will probably change, and
it's important not to change the returned caveat.
#### func (*Macaroon) Clone
```go
func (m *Macaroon) Clone() *Macaroon
```
Clone returns a copy of the receiving macaroon.
#### func (*Macaroon) Id
```go
func (m *Macaroon) Id() string
```
Id returns the id of the macaroon. This can hold arbitrary information.
#### func (*Macaroon) Location
```go
func (m *Macaroon) Location() string
```
Location returns the macaroon's location hint. This is not verified as part of
the macaroon.
#### func (*Macaroon) MarshalBinary
```go
func (m *Macaroon) MarshalBinary() ([]byte, error)
```
MarshalBinary implements encoding.BinaryMarshaler.
#### func (*Macaroon) MarshalJSON
```go
func (m *Macaroon) MarshalJSON() ([]byte, error)
```
MarshalJSON implements json.Marshaler.
#### func (*Macaroon) Signature
```go
func (m *Macaroon) Signature() []byte
```
Signature returns the macaroon's signature.
#### func (*Macaroon) UnmarshalBinary
```go
func (m *Macaroon) UnmarshalBinary(data []byte) error
```
UnmarshalBinary implements encoding.BinaryUnmarshaler.
#### func (*Macaroon) UnmarshalJSON
```go
func (m *Macaroon) UnmarshalJSON(jsonData []byte) error
```
UnmarshalJSON implements json.Unmarshaler.
#### func (*Macaroon) Verify
```go
func (m *Macaroon) Verify(rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error
```
Verify verifies that the receiving macaroon is valid. The root key must be the
same that the macaroon was originally minted with. The check function is called
to verify each first-party caveat - it should return an error if the condition
is not met.
The discharge macaroons should be provided in discharges.
Verify returns true if the verification succeeds; if returns (false, nil) if the
verification fails, and (false, err) if the verification cannot be asserted (but
may not be false).
TODO(rog) is there a possible DOS attack that can cause this function to
infinitely recurse?
#### type Verifier
```go
type Verifier interface {
Verify(m *Macaroon, rootKey []byte) (bool, error)
}
```

View file

@ -0,0 +1,4 @@
macaroon:
- verify that all signature calculations to correspond exactly
with libmacaroons.

View file

@ -0,0 +1,109 @@
package macaroon_test
import (
"crypto/rand"
"encoding/base64"
"testing"
"gopkg.in/macaroon.v2"
)
func randomBytes(n int) []byte {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
panic(err)
}
return b
}
func BenchmarkNew(b *testing.B) {
rootKey := randomBytes(24)
id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100)))
loc := base64.StdEncoding.EncodeToString(randomBytes(40))
b.ResetTimer()
for i := b.N - 1; i >= 0; i-- {
MustNew(rootKey, id, loc, macaroon.LatestVersion)
}
}
func BenchmarkAddCaveat(b *testing.B) {
rootKey := randomBytes(24)
id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100)))
loc := base64.StdEncoding.EncodeToString(randomBytes(40))
b.ResetTimer()
for i := b.N - 1; i >= 0; i-- {
b.StopTimer()
m := MustNew(rootKey, id, loc, macaroon.LatestVersion)
b.StartTimer()
m.AddFirstPartyCaveat([]byte("some caveat stuff"))
}
}
func benchmarkVerify(b *testing.B, mspecs []macaroonSpec) {
rootKey, macaroons := makeMacaroons(mspecs)
check := func(string) error {
return nil
}
b.ResetTimer()
for i := b.N - 1; i >= 0; i-- {
err := macaroons[0].Verify(rootKey, check, macaroons[1:])
if err != nil {
b.Fatalf("verification failed: %v", err)
}
}
}
func BenchmarkVerifyLarge(b *testing.B) {
benchmarkVerify(b, multilevelThirdPartyCaveatMacaroons)
}
func BenchmarkVerifySmall(b *testing.B) {
benchmarkVerify(b, []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}},
}})
}
func BenchmarkMarshalJSON(b *testing.B) {
rootKey := randomBytes(24)
id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100)))
loc := base64.StdEncoding.EncodeToString(randomBytes(40))
m := MustNew(rootKey, id, loc, macaroon.LatestVersion)
b.ResetTimer()
for i := b.N - 1; i >= 0; i-- {
_, err := m.MarshalJSON()
if err != nil {
b.Fatalf("cannot marshal JSON: %v", err)
}
}
}
func MustNew(rootKey, id []byte, loc string, vers macaroon.Version) *macaroon.Macaroon {
m, err := macaroon.New(rootKey, id, loc, vers)
if err != nil {
panic(err)
}
return m
}
func BenchmarkUnmarshalJSON(b *testing.B) {
rootKey := randomBytes(24)
id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100)))
loc := base64.StdEncoding.EncodeToString(randomBytes(40))
m := MustNew(rootKey, id, loc, macaroon.LatestVersion)
data, err := m.MarshalJSON()
if err != nil {
b.Fatalf("cannot marshal JSON: %v", err)
}
for i := b.N - 1; i >= 0; i-- {
var m macaroon.Macaroon
err := m.UnmarshalJSON(data)
if err != nil {
b.Fatalf("cannot unmarshal JSON: %v", err)
}
}
}

View file

@ -0,0 +1,91 @@
package macaroon
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
"hash"
"io"
"golang.org/x/crypto/nacl/secretbox"
)
func keyedHash(key *[hashLen]byte, text []byte) *[hashLen]byte {
h := keyedHasher(key)
h.Write([]byte(text))
var sum [hashLen]byte
hashSum(h, &sum)
return &sum
}
func keyedHasher(key *[hashLen]byte) hash.Hash {
return hmac.New(sha256.New, key[:])
}
var keyGen = []byte("macaroons-key-generator")
// makeKey derives a fixed length key from a variable
// length key. The keyGen constant is the same
// as that used in libmacaroons.
func makeKey(variableKey []byte) *[keyLen]byte {
h := hmac.New(sha256.New, keyGen)
h.Write(variableKey)
var key [keyLen]byte
hashSum(h, &key)
return &key
}
// hashSum calls h.Sum to put the sum into
// the given destination. It also sanity
// checks that the result really is the expected
// size.
func hashSum(h hash.Hash, dest *[hashLen]byte) {
r := h.Sum(dest[:0])
if len(r) != len(dest) {
panic("hash size inconsistency")
}
}
const (
keyLen = 32
nonceLen = 24
hashLen = sha256.Size
)
func newNonce(r io.Reader) (*[nonceLen]byte, error) {
var nonce [nonceLen]byte
_, err := r.Read(nonce[:])
if err != nil {
return nil, fmt.Errorf("cannot generate random bytes: %v", err)
}
return &nonce, nil
}
func encrypt(key *[keyLen]byte, text *[hashLen]byte, r io.Reader) ([]byte, error) {
nonce, err := newNonce(r)
if err != nil {
return nil, err
}
out := make([]byte, 0, len(nonce)+secretbox.Overhead+len(text))
out = append(out, nonce[:]...)
return secretbox.Seal(out, text[:], nonce, key), nil
}
func decrypt(key *[keyLen]byte, ciphertext []byte) (*[hashLen]byte, error) {
if len(ciphertext) < nonceLen+secretbox.Overhead {
return nil, fmt.Errorf("message too short")
}
var nonce [nonceLen]byte
copy(nonce[:], ciphertext)
ciphertext = ciphertext[nonceLen:]
text, ok := secretbox.Open(nil, ciphertext, &nonce, key)
if !ok {
return nil, fmt.Errorf("decryption failure")
}
if len(text) != hashLen {
return nil, fmt.Errorf("decrypted text is wrong length")
}
var rtext [hashLen]byte
copy(rtext[:], text)
return &rtext, nil
}

View file

@ -0,0 +1,66 @@
package macaroon
import (
"crypto/rand"
"fmt"
"golang.org/x/crypto/nacl/secretbox"
gc "gopkg.in/check.v1"
)
type cryptoSuite struct{}
var _ = gc.Suite(&cryptoSuite{})
var testCryptKey = &[hashLen]byte{'k', 'e', 'y'}
var testCryptText = &[hashLen]byte{'t', 'e', 'x', 't'}
func (*cryptoSuite) TestEncDec(c *gc.C) {
b, err := encrypt(testCryptKey, testCryptText, rand.Reader)
c.Assert(err, gc.IsNil)
t, err := decrypt(testCryptKey, b)
c.Assert(err, gc.IsNil)
c.Assert(string(t[:]), gc.Equals, string(testCryptText[:]))
}
func (*cryptoSuite) TestUniqueNonces(c *gc.C) {
nonces := make(map[string]struct{})
for i := 0; i < 100; i++ {
nonce, err := newNonce(rand.Reader)
c.Assert(err, gc.IsNil)
nonces[string(nonce[:])] = struct{}{}
}
c.Assert(nonces, gc.HasLen, 100, gc.Commentf("duplicate nonce detected"))
}
type ErrorReader struct{}
func (*ErrorReader) Read([]byte) (int, error) {
return 0, fmt.Errorf("fail")
}
func (*cryptoSuite) TestBadRandom(c *gc.C) {
_, err := newNonce(&ErrorReader{})
c.Assert(err, gc.ErrorMatches, "^cannot generate random bytes:.*")
_, err = encrypt(testCryptKey, testCryptText, &ErrorReader{})
c.Assert(err, gc.ErrorMatches, "^cannot generate random bytes:.*")
}
func (*cryptoSuite) TestBadCiphertext(c *gc.C) {
buf := randomBytes(nonceLen + secretbox.Overhead)
for i := range buf {
_, err := decrypt(testCryptKey, buf[0:i])
c.Assert(err, gc.ErrorMatches, "message too short")
}
_, err := decrypt(testCryptKey, buf)
c.Assert(err, gc.ErrorMatches, "decryption failure")
}
func randomBytes(n int) []byte {
buf := make([]byte, n)
if _, err := rand.Reader.Read(buf); err != nil {
panic(err)
}
return buf
}

View file

@ -0,0 +1,14 @@
package macaroon
var (
AddThirdPartyCaveatWithRand = (*Macaroon).addThirdPartyCaveatWithRand
)
type MacaroonJSONV2 macaroonJSONV2
// SetVersion sets the version field of m to v;
// usually so that we can compare it for deep equality with
// another differently unmarshaled macaroon.
func (m *Macaroon) SetVersion(v Version) {
m.version = v
}

View file

@ -0,0 +1,366 @@
// The macaroon package implements macaroons as described in
// the paper "Macaroons: Cookies with Contextual Caveats for
// Decentralized Authorization in the Cloud"
// (http://theory.stanford.edu/~ataly/Papers/macaroons.pdf)
//
// See the macaroon bakery packages at http://godoc.org/gopkg.in/macaroon-bakery.v1
// for higher level services and operations that use macaroons.
package macaroon
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"fmt"
"io"
"unicode/utf8"
)
// Macaroon holds a macaroon.
// See Fig. 7 of http://theory.stanford.edu/~ataly/Papers/macaroons.pdf
// for a description of the data contained within.
// Macaroons are mutable objects - use Clone as appropriate
// to avoid unwanted mutation.
type Macaroon struct {
location string
id []byte
caveats []Caveat
sig [hashLen]byte
version Version
}
// Caveat holds a first person or third party caveat.
type Caveat struct {
// Id holds the id of the caveat. For first
// party caveats this holds the condition;
// for third party caveats this holds the encrypted
// third party caveat.
Id []byte
// VerificationId holds the verification id. If this is
// non-empty, it's a third party caveat.
VerificationId []byte
// For third-party caveats, Location holds the
// ocation hint. Note that this is not signature checked
// as part of the caveat, so should only
// be used as a hint.
Location string
}
// isThirdParty reports whether the caveat must be satisfied
// by some third party (if not, it's a first person caveat).
func (cav *Caveat) isThirdParty() bool {
return len(cav.VerificationId) > 0
}
// New returns a new macaroon with the given root key,
// identifier, location and version.
func New(rootKey, id []byte, loc string, version Version) (*Macaroon, error) {
var m Macaroon
if version < V2 {
if !utf8.Valid(id) {
return nil, fmt.Errorf("invalid id for %v macaroon", id)
}
// TODO check id length too.
}
if version < V1 || version > LatestVersion {
return nil, fmt.Errorf("invalid version %v", version)
}
m.version = version
m.init(append([]byte(nil), id...), loc, version)
derivedKey := makeKey(rootKey)
m.sig = *keyedHash(derivedKey, m.id)
return &m, nil
}
// init initializes the macaroon. It retains a reference to id.
func (m *Macaroon) init(id []byte, loc string, vers Version) {
m.location = loc
m.id = append([]byte(nil), id...)
m.version = vers
}
// SetLocation sets the location associated with the macaroon.
// Note that the location is not included in the macaroon's
// hash chain, so this does not change the signature.
func (m *Macaroon) SetLocation(loc string) {
m.location = loc
}
// Clone returns a copy of the receiving macaroon.
func (m *Macaroon) Clone() *Macaroon {
m1 := *m
// Ensure that if any caveats are appended to the new
// macaroon, it will copy the caveats.
m1.caveats = m1.caveats[0:len(m1.caveats):len(m1.caveats)]
return &m1
}
// Location returns the macaroon's location hint. This is
// not verified as part of the macaroon.
func (m *Macaroon) Location() string {
return m.location
}
// Id returns the id of the macaroon. This can hold
// arbitrary information.
func (m *Macaroon) Id() []byte {
return append([]byte(nil), m.id...)
}
// Signature returns the macaroon's signature.
func (m *Macaroon) Signature() []byte {
// sig := m.sig
// return sig[:]
// Work around https://github.com/golang/go/issues/9537
sig := new([hashLen]byte)
*sig = m.sig
return sig[:]
}
// Caveats returns the macaroon's caveats.
// This method will probably change, and it's important not to change the returned caveat.
func (m *Macaroon) Caveats() []Caveat {
return m.caveats[0:len(m.caveats):len(m.caveats)]
}
// appendCaveat appends a caveat without modifying the macaroon's signature.
func (m *Macaroon) appendCaveat(caveatId, verificationId []byte, loc string) {
m.caveats = append(m.caveats, Caveat{
Id: caveatId,
VerificationId: verificationId,
Location: loc,
})
}
func (m *Macaroon) addCaveat(caveatId, verificationId []byte, loc string) error {
if m.version < V2 {
if !utf8.Valid(caveatId) {
return fmt.Errorf("invalid caveat id for %v macaroon", m.version)
}
// TODO check caveat length too.
}
m.appendCaveat(caveatId, verificationId, loc)
if len(verificationId) == 0 {
m.sig = *keyedHash(&m.sig, caveatId)
} else {
m.sig = *keyedHash2(&m.sig, verificationId, caveatId)
}
return nil
}
func keyedHash2(key *[keyLen]byte, d1, d2 []byte) *[hashLen]byte {
var data [hashLen * 2]byte
copy(data[0:], keyedHash(key, d1)[:])
copy(data[hashLen:], keyedHash(key, d2)[:])
return keyedHash(key, data[:])
}
// Bind prepares the macaroon for being used to discharge the
// macaroon with the given signature sig. This must be
// used before it is used in the discharges argument to Verify.
func (m *Macaroon) Bind(sig []byte) {
m.sig = *bindForRequest(sig, &m.sig)
}
// AddFirstPartyCaveat adds a caveat that will be verified
// by the target service.
func (m *Macaroon) AddFirstPartyCaveat(condition []byte) error {
m.addCaveat(condition, nil, "")
return nil
}
// AddThirdPartyCaveat adds a third-party caveat to the macaroon,
// using the given shared root key, caveat id and location hint.
// The caveat id should encode the root key in some
// way, either by encrypting it with a key known to the third party
// or by holding a reference to it stored in the third party's
// storage.
func (m *Macaroon) AddThirdPartyCaveat(rootKey, caveatId []byte, loc string) error {
return m.addThirdPartyCaveatWithRand(rootKey, caveatId, loc, rand.Reader)
}
// addThirdPartyCaveatWithRand adds a third-party caveat to the macaroon, using
// the given source of randomness for encrypting the caveat id.
func (m *Macaroon) addThirdPartyCaveatWithRand(rootKey, caveatId []byte, loc string, r io.Reader) error {
derivedKey := makeKey(rootKey)
verificationId, err := encrypt(&m.sig, derivedKey, r)
if err != nil {
return err
}
m.addCaveat(caveatId, verificationId, loc)
return nil
}
var zeroKey [hashLen]byte
// bindForRequest binds the given macaroon
// to the given signature of its parent macaroon.
func bindForRequest(rootSig []byte, dischargeSig *[hashLen]byte) *[hashLen]byte {
if bytes.Equal(rootSig, dischargeSig[:]) {
return dischargeSig
}
return keyedHash2(&zeroKey, rootSig, dischargeSig[:])
}
// Verify verifies that the receiving macaroon is valid.
// The root key must be the same that the macaroon was originally
// minted with. The check function is called to verify each
// first-party caveat - it should return an error if the
// condition is not met.
//
// The discharge macaroons should be provided in discharges.
//
// Verify returns nil if the verification succeeds.
func (m *Macaroon) Verify(rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error {
var vctx verificationContext
vctx.init(rootKey, m, discharges, check)
return vctx.verify(m, rootKey)
}
// VerifySignature verifies the signature of the given macaroon with respect
// to the root key, but it does not validate any first-party caveats. Instead
// it returns all the applicable first party caveats on success.
//
// The caller is responsible for checking the returned first party caveat
// conditions.
func (m *Macaroon) VerifySignature(rootKey []byte, discharges []*Macaroon) ([]string, error) {
n := len(m.caveats)
for _, dm := range discharges {
n += len(dm.caveats)
}
conds := make([]string, 0, n)
var vctx verificationContext
vctx.init(rootKey, m, discharges, func(cond string) error {
conds = append(conds, cond)
return nil
})
err := vctx.verify(m, rootKey)
if err != nil {
return nil, err
}
return conds, nil
}
// TraceVerify verifies the signature of the macaroon without checking
// any of the first party caveats, and returns a slice of Traces holding
// the operations used when verifying the macaroons.
//
// Each element in the returned slice corresponds to the
// operation for one of the argument macaroons, with m at index 0,
// and discharges at 1 onwards.
func (m *Macaroon) TraceVerify(rootKey []byte, discharges []*Macaroon) ([]Trace, error) {
var vctx verificationContext
vctx.init(rootKey, m, discharges, func(string) error { return nil })
vctx.traces = make([]Trace, len(discharges)+1)
err := vctx.verify(m, rootKey)
return vctx.traces, err
}
type verificationContext struct {
used []bool
discharges []*Macaroon
rootSig *[hashLen]byte
traces []Trace
check func(caveat string) error
}
func (vctx *verificationContext) init(rootKey []byte, root *Macaroon, discharges []*Macaroon, check func(caveat string) error) {
*vctx = verificationContext{
discharges: discharges,
used: make([]bool, len(discharges)),
rootSig: &root.sig,
check: check,
}
}
func (vctx *verificationContext) verify(root *Macaroon, rootKey []byte) error {
vctx.traceRootKey(0, rootKey)
vctx.trace(0, TraceMakeKey, rootKey, nil)
derivedKey := makeKey(rootKey)
if err := vctx.verify0(root, 0, derivedKey); err != nil {
vctx.trace(0, TraceFail, nil, nil)
return err
}
for i, wasUsed := range vctx.used {
if !wasUsed {
vctx.trace(i+1, TraceFail, nil, nil)
return fmt.Errorf("discharge macaroon %q was not used", vctx.discharges[i].Id())
}
}
return nil
}
func (vctx *verificationContext) verify0(m *Macaroon, index int, rootKey *[hashLen]byte) error {
vctx.trace(index, TraceHash, m.id, nil)
caveatSig := keyedHash(rootKey, m.id)
for i, cav := range m.caveats {
if cav.isThirdParty() {
cavKey, err := decrypt(caveatSig, cav.VerificationId)
if err != nil {
return fmt.Errorf("failed to decrypt caveat %d signature: %v", i, err)
}
dm, di, err := vctx.findDischarge(cav.Id)
if err != nil {
return err
}
vctx.traceRootKey(di+1, cavKey[:])
if err := vctx.verify0(dm, di+1, cavKey); err != nil {
vctx.trace(di+1, TraceFail, nil, nil)
return err
}
vctx.trace(index, TraceHash, cav.VerificationId, cav.Id)
caveatSig = keyedHash2(caveatSig, cav.VerificationId, cav.Id)
} else {
vctx.trace(index, TraceHash, cav.Id, nil)
caveatSig = keyedHash(caveatSig, cav.Id)
if err := vctx.check(string(cav.Id)); err != nil {
return err
}
}
}
if index > 0 {
vctx.trace(index, TraceBind, vctx.rootSig[:], caveatSig[:])
caveatSig = bindForRequest(vctx.rootSig[:], caveatSig)
}
// TODO perhaps we should actually do this check before doing
// all the potentially expensive caveat checks.
if !hmac.Equal(caveatSig[:], m.sig[:]) {
return fmt.Errorf("signature mismatch after caveat verification")
}
return nil
}
func (vctx *verificationContext) findDischarge(id []byte) (dm *Macaroon, index int, err error) {
for di, dm := range vctx.discharges {
if !bytes.Equal(dm.id, id) {
continue
}
// Don't use a discharge macaroon more than once.
// It's important that we do this check here rather than after
// verify as it prevents potentially infinite recursion.
if vctx.used[di] {
return nil, 0, fmt.Errorf("discharge macaroon %q was used more than once", dm.Id())
}
vctx.used[di] = true
return dm, di, nil
}
return nil, 0, fmt.Errorf("cannot find discharge macaroon for caveat %x", id)
}
func (vctx *verificationContext) trace(index int, op TraceOpKind, data1, data2 []byte) {
if vctx.traces != nil {
vctx.traces[index].Ops = append(vctx.traces[index].Ops, TraceOp{
Kind: op,
Data1: data1,
Data2: data2,
})
}
}
func (vctx *verificationContext) traceRootKey(index int, rootKey []byte) {
if vctx.traces != nil {
vctx.traces[index].RootKey = rootKey[:]
}
}

View file

@ -0,0 +1,914 @@
package macaroon_test
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"testing"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"gopkg.in/macaroon.v2"
)
func TestPackage(t *testing.T) {
gc.TestingT(t)
}
type macaroonSuite struct{}
var _ = gc.Suite(&macaroonSuite{})
func never(string) error {
return fmt.Errorf("condition is never true")
}
func (*macaroonSuite) TestNoCaveats(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
c.Assert(m.Location(), gc.Equals, "a location")
c.Assert(m.Id(), gc.DeepEquals, []byte("some id"))
err := m.Verify(rootKey, never, nil)
c.Assert(err, gc.IsNil)
}
func (*macaroonSuite) TestFirstPartyCaveat(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
caveats := map[string]bool{
"a caveat": true,
"another caveat": true,
}
tested := make(map[string]bool)
for cav := range caveats {
m.AddFirstPartyCaveat([]byte(cav))
}
expectErr := fmt.Errorf("condition not met")
check := func(cav string) error {
tested[cav] = true
if caveats[cav] {
return nil
}
return expectErr
}
err := m.Verify(rootKey, check, nil)
c.Assert(err, gc.IsNil)
c.Assert(tested, gc.DeepEquals, caveats)
m.AddFirstPartyCaveat([]byte("not met"))
err = m.Verify(rootKey, check, nil)
c.Assert(err, gc.Equals, expectErr)
c.Assert(tested["not met"], gc.Equals, true)
}
func (*macaroonSuite) TestThirdPartyCaveat(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
dischargeRootKey := []byte("shared root key")
thirdPartyCaveatId := []byte("3rd party caveat")
err := m.AddThirdPartyCaveat(dischargeRootKey, thirdPartyCaveatId, "remote.com")
c.Assert(err, gc.IsNil)
dm := MustNew(dischargeRootKey, thirdPartyCaveatId, "remote location", macaroon.LatestVersion)
dm.Bind(m.Signature())
err = m.Verify(rootKey, never, []*macaroon.Macaroon{dm})
c.Assert(err, gc.IsNil)
}
func (*macaroonSuite) TestThirdPartyCaveatBadRandom(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
dischargeRootKey := []byte("shared root key")
thirdPartyCaveatId := []byte("3rd party caveat")
err := macaroon.AddThirdPartyCaveatWithRand(m, dischargeRootKey, thirdPartyCaveatId, "remote.com", &macaroon.ErrorReader{})
c.Assert(err, gc.ErrorMatches, "cannot generate random bytes: fail")
}
func (*macaroonSuite) TestSetLocation(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
c.Assert(m.Location(), gc.Equals, "a location")
m.SetLocation("another location")
c.Assert(m.Location(), gc.Equals, "another location")
}
type conditionTest struct {
conditions map[string]bool
expectErr string
}
var verifyTests = []struct {
about string
macaroons []macaroonSpec
conditions []conditionTest
}{{
about: "single third party caveat without discharge",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
expectErr: fmt.Sprintf(`cannot find discharge macaroon for caveat %x`, "bob-is-great"),
}},
}, {
about: "single third party caveat with discharge",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
}, {
conditions: map[string]bool{
"wonderful": false,
},
expectErr: `condition "wonderful" not met`,
}},
}, {
about: "single third party caveat with discharge with mismatching root key",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key-wrong",
id: "bob-is-great",
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
expectErr: `signature mismatch after caveat verification`,
}},
}, {
about: "single third party caveat with two discharges",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "top of the world",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
expectErr: `condition "splendid" not met`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": true,
},
expectErr: `discharge macaroon "bob-is-great" was not used`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": false,
"top of the world": true,
},
expectErr: `condition "splendid" not met`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": false,
},
expectErr: `discharge macaroon "bob-is-great" was not used`,
}},
}, {
about: "one discharge used for two macaroons",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "somewhere else",
location: "bob",
rootKey: "bob-caveat-root-key",
}, {
condition: "bob-is-great",
location: "charlie",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "somewhere else",
caveats: []caveat{{
condition: "bob-is-great",
location: "charlie",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
}},
conditions: []conditionTest{{
expectErr: `discharge macaroon "bob-is-great" was used more than once`,
}},
}, {
about: "recursive third party caveat",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "bob-is-great",
location: "charlie",
rootKey: "bob-caveat-root-key",
}},
}},
conditions: []conditionTest{{
expectErr: `discharge macaroon "bob-is-great" was used more than once`,
}},
}, {
about: "two third party caveats",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}, {
condition: "charlie-is-great",
location: "charlie",
rootKey: "charlie-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}},
}, {
location: "charlie",
rootKey: "charlie-caveat-root-key",
id: "charlie-is-great",
caveats: []caveat{{
condition: "top of the world",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": true,
},
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": false,
"top of the world": true,
},
expectErr: `condition "splendid" not met`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": false,
},
expectErr: `condition "top of the world" not met`,
}},
}, {
about: "third party caveat with undischarged third party caveat",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}, {
condition: "barbara-is-great",
location: "barbara",
rootKey: "barbara-caveat-root-key",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
},
expectErr: fmt.Sprintf(`cannot find discharge macaroon for caveat %x`, "barbara-is-great"),
}},
}, {
about: "multilevel third party caveats",
macaroons: multilevelThirdPartyCaveatMacaroons,
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"high-fiving": true,
"spiffing": true,
},
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"high-fiving": false,
"spiffing": true,
},
expectErr: `condition "high-fiving" not met`,
}},
}, {
about: "unused discharge",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
}, {
rootKey: "other-key",
id: "unused",
}},
conditions: []conditionTest{{
expectErr: `discharge macaroon "unused" was not used`,
}},
}}
var multilevelThirdPartyCaveatMacaroons = []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}, {
condition: "charlie-is-great",
location: "charlie",
rootKey: "charlie-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}, {
condition: "barbara-is-great",
location: "barbara",
rootKey: "barbara-caveat-root-key",
}},
}, {
location: "charlie",
rootKey: "charlie-caveat-root-key",
id: "charlie-is-great",
caveats: []caveat{{
condition: "splendid",
}, {
condition: "celine-is-great",
location: "celine",
rootKey: "celine-caveat-root-key",
}},
}, {
location: "barbara",
rootKey: "barbara-caveat-root-key",
id: "barbara-is-great",
caveats: []caveat{{
condition: "spiffing",
}, {
condition: "ben-is-great",
location: "ben",
rootKey: "ben-caveat-root-key",
}},
}, {
location: "ben",
rootKey: "ben-caveat-root-key",
id: "ben-is-great",
}, {
location: "celine",
rootKey: "celine-caveat-root-key",
id: "celine-is-great",
caveats: []caveat{{
condition: "high-fiving",
}},
}}
func (*macaroonSuite) TestVerify(c *gc.C) {
for i, test := range verifyTests {
c.Logf("test %d: %s", i, test.about)
rootKey, macaroons := makeMacaroons(test.macaroons)
for _, cond := range test.conditions {
c.Logf("conditions %#v", cond.conditions)
check := func(cav string) error {
if cond.conditions[cav] {
return nil
}
return fmt.Errorf("condition %q not met", cav)
}
err := macaroons[0].Verify(
rootKey,
check,
macaroons[1:],
)
if cond.expectErr != "" {
c.Assert(err, gc.ErrorMatches, cond.expectErr)
} else {
c.Assert(err, gc.IsNil)
}
// Cloned macaroon should have same verify result.
cloneErr := macaroons[0].Clone().Verify(rootKey, check, macaroons[1:])
c.Assert(cloneErr, gc.DeepEquals, err)
}
}
}
func (*macaroonSuite) TestTraceVerify(c *gc.C) {
rootKey, macaroons := makeMacaroons(multilevelThirdPartyCaveatMacaroons)
traces, err := macaroons[0].TraceVerify(rootKey, macaroons[1:])
c.Assert(err, gc.Equals, nil)
c.Assert(traces, gc.HasLen, len(macaroons))
// Check that we can run through the resulting operations and
// arrive at the same signature.
for i, m := range macaroons {
r := traces[i].Results()
c.Assert(b64str(r[len(r)-1]), gc.Equals, b64str(m.Signature()), gc.Commentf("macaroon %d", i))
}
}
func (*macaroonSuite) TestTraceVerifyFailure(c *gc.C) {
rootKey, macaroons := makeMacaroons([]macaroonSpec{{
rootKey: "xxx",
id: "hello",
caveats: []caveat{{
condition: "cond1",
}, {
condition: "cond2",
}, {
condition: "cond3",
}},
}})
// Marshal the macaroon, corrupt a condition, then unmarshal
// it and check we see the expected trace failure.
data, err := json.Marshal(macaroons[0])
c.Assert(err, gc.Equals, nil)
var jm macaroon.MacaroonJSONV2
err = json.Unmarshal(data, &jm)
c.Assert(err, gc.Equals, nil)
jm.Caveats[1].CID = "cond2 corrupted"
data, err = json.Marshal(jm)
c.Assert(err, gc.Equals, nil)
var corruptm *macaroon.Macaroon
err = json.Unmarshal(data, &corruptm)
c.Assert(err, gc.Equals, nil)
traces, err := corruptm.TraceVerify(rootKey, nil)
c.Assert(err, gc.ErrorMatches, `signature mismatch after caveat verification`)
c.Assert(traces, gc.HasLen, 1)
var kinds []macaroon.TraceOpKind
for _, op := range traces[0].Ops {
kinds = append(kinds, op.Kind)
}
c.Assert(kinds, gc.DeepEquals, []macaroon.TraceOpKind{
macaroon.TraceMakeKey,
macaroon.TraceHash, // id
macaroon.TraceHash, // cond1
macaroon.TraceHash, // cond2
macaroon.TraceHash, // cond3
macaroon.TraceFail, // sig mismatch
})
}
func b64str(b []byte) string {
return base64.StdEncoding.EncodeToString(b)
}
func (*macaroonSuite) TestVerifySignature(c *gc.C) {
rootKey, macaroons := makeMacaroons([]macaroonSpec{{
rootKey: "xxx",
id: "hello",
caveats: []caveat{{
rootKey: "y",
condition: "something",
location: "somewhere",
}, {
condition: "cond1",
}, {
condition: "cond2",
}},
}, {
rootKey: "y",
id: "something",
caveats: []caveat{{
condition: "cond3",
}, {
condition: "cond4",
}},
}})
conds, err := macaroons[0].VerifySignature(rootKey, macaroons[1:])
c.Assert(err, gc.IsNil)
c.Assert(conds, jc.DeepEquals, []string{"cond3", "cond4", "cond1", "cond2"})
conds, err = macaroons[0].VerifySignature(nil, macaroons[1:])
c.Assert(err, gc.ErrorMatches, `failed to decrypt caveat 0 signature: decryption failure`)
c.Assert(conds, gc.IsNil)
}
// TODO(rog) move the following JSON-marshal tests into marshal_test.go.
// jsonTestVersions holds the various possible ways of marshaling a macaroon
// to JSON.
var jsonTestVersions = []macaroon.Version{
macaroon.V1,
macaroon.V2,
}
func (s *macaroonSuite) TestMarshalJSON(c *gc.C) {
for i, vers := range jsonTestVersions {
c.Logf("test %d: %v", i, vers)
s.testMarshalJSONWithVersion(c, vers)
}
}
func (*macaroonSuite) testMarshalJSONWithVersion(c *gc.C, vers macaroon.Version) {
rootKey := []byte("secret")
m0 := MustNew(rootKey, []byte("some id"), "a location", vers)
m0.AddFirstPartyCaveat([]byte("account = 3735928559"))
m0JSON, err := json.Marshal(m0)
c.Assert(err, gc.IsNil)
var m1 macaroon.Macaroon
err = json.Unmarshal(m0JSON, &m1)
c.Assert(err, gc.IsNil)
c.Assert(m0.Location(), gc.Equals, m1.Location())
c.Assert(string(m0.Id()), gc.Equals, string(m1.Id()))
c.Assert(
hex.EncodeToString(m0.Signature()),
gc.Equals,
hex.EncodeToString(m1.Signature()))
c.Assert(m1.Version(), gc.Equals, vers)
}
var jsonRoundTripTests = []struct {
about string
// data holds the marshaled data. All the data values hold
// different encodings of the same macaroon - the same as produced
// from the second example in libmacaroons
// example README with the following libmacaroons code:
//
// secret = 'this is a different super-secret key; never use the same secret twice'
// public = 'we used our other secret key'
// location = 'http://mybank/'
// M = macaroons.create(location, secret, public)
// M = M.add_first_party_caveat('account = 3735928559')
// caveat_key = '4; guaranteed random by a fair toss of the dice'
// predicate = 'user = Alice'
// identifier = 'this was how we remind auth of key/pred'
// M = M.add_third_party_caveat('http://auth.mybank/', caveat_key, identifier)
// m.serialize_json()
data string
expectExactRoundTrip bool
expectVers macaroon.Version
}{{
about: "exact JSON as produced by libmacaroons",
data: `{"caveats":[{"cid":"account = 3735928559"},{"cid":"this was how we remind auth of key\/pred","vid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","cl":"http:\/\/auth.mybank\/"}],"location":"http:\/\/mybank\/","identifier":"we used our other secret key","signature":"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c"}`,
expectVers: macaroon.V1,
expectExactRoundTrip: true,
}, {
about: "V2 object with std base-64 binary values",
data: `{"c":[{"i64":"YWNjb3VudCA9IDM3MzU5Mjg1NTk="},{"i64":"dGhpcyB3YXMgaG93IHdlIHJlbWluZCBhdXRoIG9mIGtleS9wcmVk","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD/w/dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i64":"d2UgdXNlZCBvdXIgb3RoZXIgc2VjcmV0IGtleQ==","s64":"0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw="}`,
expectVers: macaroon.V2,
}, {
about: "V2 object with URL base-64 binary values",
data: `{"c":[{"i64":"YWNjb3VudCA9IDM3MzU5Mjg1NTk"},{"i64":"dGhpcyB3YXMgaG93IHdlIHJlbWluZCBhdXRoIG9mIGtleS9wcmVk","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i64":"d2UgdXNlZCBvdXIgb3RoZXIgc2VjcmV0IGtleQ","s64":"0n2y_R8idg5MPa6BN-LY_B32wHQcGK7UuXJWv3jR9Vw"}`,
expectVers: macaroon.V2,
}, {
about: "V2 object with URL base-64 binary values and strings for ASCII",
data: `{"c":[{"i":"account = 3735928559"},{"i":"this was how we remind auth of key/pred","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i":"we used our other secret key","s64":"0n2y_R8idg5MPa6BN-LY_B32wHQcGK7UuXJWv3jR9Vw"}`,
expectVers: macaroon.V2,
expectExactRoundTrip: true,
}, {
about: "V2 base64 encoded binary",
data: `"` +
base64.StdEncoding.EncodeToString([]byte(
"\x02"+
"\x01\x0ehttp://mybank/"+
"\x02\x1cwe used our other secret key"+
"\x00"+
"\x02\x14account = 3735928559"+
"\x00"+
"\x01\x13http://auth.mybank/"+
"\x02'this was how we remind auth of key/pred"+
"\x04\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x6e\xc5\x02\xe0\x58\x86\xd1\xf0\x27\x9f\x05\x5f\xa5\x25\x54\xd1\x6d\x16\xc1\xb1\x40\x74\xbb\xb8\x3f\xf0\xfd\xd7\x9d\xc2\xfe\x09\x8f\x0e\xd4\xa2\xb0\x91\x13\x0e\x6b\x5d\xb4\x6a\x20\xa8\x6b"+
"\x00"+
"\x00"+
"\x06\x20\xd2\x7d\xb2\xfd\x1f\x22\x76\x0e\x4c\x3d\xae\x81\x37\xe2\xd8\xfc\x1d\xf6\xc0\x74\x1c\x18\xae\xd4\xb9\x72\x56\xbf\x78\xd1\xf5\x5c",
)) + `"`,
expectVers: macaroon.V2,
}}
func (s *macaroonSuite) TestJSONRoundTrip(c *gc.C) {
for i, test := range jsonRoundTripTests {
c.Logf("test %d (%v) %s", i, test.expectVers, test.about)
s.testJSONRoundTripWithVersion(c, test.data, test.expectVers, test.expectExactRoundTrip)
}
}
func (*macaroonSuite) testJSONRoundTripWithVersion(c *gc.C, jsonData string, vers macaroon.Version, expectExactRoundTrip bool) {
var m macaroon.Macaroon
err := json.Unmarshal([]byte(jsonData), &m)
c.Assert(err, gc.IsNil)
assertLibMacaroonsMacaroon(c, &m)
c.Assert(m.Version(), gc.Equals, vers)
data, err := m.MarshalJSON()
c.Assert(err, gc.IsNil)
if expectExactRoundTrip {
// The data is in canonical form, so we can check that
// the round-tripped data is the same as the original
// data when unmarshalled into an interface{}.
var got interface{}
err = json.Unmarshal(data, &got)
c.Assert(err, gc.IsNil)
var original interface{}
err = json.Unmarshal([]byte(jsonData), &original)
c.Assert(err, gc.IsNil)
c.Assert(got, jc.DeepEquals, original, gc.Commentf("data: %s", data))
}
// Check that we can unmarshal the marshaled data anyway
// and the macaroon still looks the same.
var m1 macaroon.Macaroon
err = m1.UnmarshalJSON(data)
c.Assert(err, gc.IsNil)
assertLibMacaroonsMacaroon(c, &m1)
c.Assert(m.Version(), gc.Equals, vers)
}
// assertLibMacaroonsMacaroon asserts that m looks like the macaroon
// created in the README of the libmacaroons documentation.
// In particular, the signature is the same one reported there.
func assertLibMacaroonsMacaroon(c *gc.C, m *macaroon.Macaroon) {
c.Assert(fmt.Sprintf("%x", m.Signature()), gc.Equals,
"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c")
c.Assert(m.Location(), gc.Equals, "http://mybank/")
c.Assert(string(m.Id()), gc.Equals, "we used our other secret key")
c.Assert(m.Caveats(), jc.DeepEquals, []macaroon.Caveat{{
Id: []byte("account = 3735928559"),
}, {
Id: []byte("this was how we remind auth of key/pred"),
VerificationId: decodeB64("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr"),
Location: "http://auth.mybank/",
}})
}
var jsonDecodeErrorTests = []struct {
about string
data string
expectError string
}{{
about: "ambiguous id #1",
data: `{"i": "hello", "i64": "abcd", "s64": "ZDI3ZGIyZmQxZjIyNzYwZTRjM2RhZTgxMzdlMmQ4ZmMK"}`,
expectError: "invalid identifier: ambiguous field encoding",
}, {
about: "ambiguous signature",
data: `{"i": "hello", "s": "345", "s64": "543467"}`,
expectError: "invalid signature: ambiguous field encoding",
}, {
about: "signature too short",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Q"}`,
expectError: "signature has unexpected length 31",
}, {
about: "signature too long",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9dP1"}`,
expectError: "signature has unexpected length 33",
}, {
about: "invalid caveat id",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw", "c": [{"i": "hello", "i64": "00"}]}`,
expectError: "invalid cid in caveat: ambiguous field encoding",
}, {
about: "invalid caveat vid",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw", "c": [{"i": "hello", "v": "hello", "v64": "00"}]}`,
expectError: "invalid vid in caveat: ambiguous field encoding",
}}
func (*macaroonSuite) TestJSONDecodeError(c *gc.C) {
for i, test := range jsonDecodeErrorTests {
c.Logf("test %d: %v", i, test.about)
var m macaroon.Macaroon
err := json.Unmarshal([]byte(test.data), &m)
c.Assert(err, gc.ErrorMatches, test.expectError)
}
}
func (*macaroonSuite) TestFirstPartyCaveatWithInvalidUTF8(c *gc.C) {
rootKey := []byte("secret")
badString := "foo\xff"
m0 := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
err := m0.AddFirstPartyCaveat([]byte(badString))
c.Assert(err, gc.Equals, nil)
}
func decodeB64(s string) []byte {
data, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
panic(err)
}
return data
}
type caveat struct {
rootKey string
location string
condition string
}
type macaroonSpec struct {
rootKey string
id string
caveats []caveat
location string
}
func makeMacaroons(mspecs []macaroonSpec) (rootKey []byte, macaroons macaroon.Slice) {
for _, mspec := range mspecs {
macaroons = append(macaroons, makeMacaroon(mspec))
}
primary := macaroons[0]
for _, m := range macaroons[1:] {
m.Bind(primary.Signature())
}
return []byte(mspecs[0].rootKey), macaroons
}
func makeMacaroon(mspec macaroonSpec) *macaroon.Macaroon {
m := MustNew([]byte(mspec.rootKey), []byte(mspec.id), mspec.location, macaroon.LatestVersion)
for _, cav := range mspec.caveats {
if cav.location != "" {
err := m.AddThirdPartyCaveat([]byte(cav.rootKey), []byte(cav.condition), cav.location)
if err != nil {
panic(err)
}
} else {
m.AddFirstPartyCaveat([]byte(cav.condition))
}
}
return m
}
func assertEqualMacaroons(c *gc.C, m0, m1 *macaroon.Macaroon) {
m0json, err := m0.MarshalJSON()
c.Assert(err, gc.IsNil)
m1json, err := m1.MarshalJSON()
var m0val, m1val interface{}
err = json.Unmarshal(m0json, &m0val)
c.Assert(err, gc.IsNil)
err = json.Unmarshal(m1json, &m1val)
c.Assert(err, gc.IsNil)
c.Assert(m0val, gc.DeepEquals, m1val)
}
func (*macaroonSuite) TestBinaryRoundTrip(c *gc.C) {
// Test the binary marshalling and unmarshalling of a macaroon with
// first and third party caveats.
rootKey := []byte("secret")
m0 := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
err := m0.AddFirstPartyCaveat([]byte("first caveat"))
c.Assert(err, gc.IsNil)
err = m0.AddFirstPartyCaveat([]byte("second caveat"))
c.Assert(err, gc.IsNil)
err = m0.AddThirdPartyCaveat([]byte("shared root key"), []byte("3rd party caveat"), "remote.com")
c.Assert(err, gc.IsNil)
data, err := m0.MarshalBinary()
c.Assert(err, gc.IsNil)
var m1 macaroon.Macaroon
err = m1.UnmarshalBinary(data)
c.Assert(err, gc.IsNil)
assertEqualMacaroons(c, m0, &m1)
}
func (*macaroonSuite) TestBinaryMarshalingAgainstLibmacaroon(c *gc.C) {
// Test that a libmacaroon marshalled macaroon can be correctly unmarshaled
data, err := base64.RawURLEncoding.DecodeString(
"MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMmNpZGVudGlmaWVyIHdlIHVzZWQgb3VyIG90aGVyIHNlY3JldCBrZXkKMDAxZGNpZCBhY2NvdW50ID0gMzczNTkyODU1OQowMDMwY2lkIHRoaXMgd2FzIGhvdyB3ZSByZW1pbmQgYXV0aCBvZiBrZXkvcHJlZAowMDUxdmlkIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANNuxQLgWIbR8CefBV-lJVTRbRbBsUB0u7g_8P3XncL-CY8O1KKwkRMOa120aiCoawowMDFiY2wgaHR0cDovL2F1dGgubXliYW5rLwowMDJmc2lnbmF0dXJlINJ9sv0fInYOTD2ugTfi2Pwd9sB0HBiu1LlyVr940fVcCg")
c.Assert(err, gc.IsNil)
var m0 macaroon.Macaroon
err = m0.UnmarshalBinary(data)
c.Assert(err, gc.IsNil)
jsonData := []byte(`{"caveats":[{"cid":"account = 3735928559"},{"cid":"this was how we remind auth of key\/pred","vid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","cl":"http:\/\/auth.mybank\/"}],"location":"http:\/\/mybank\/","identifier":"we used our other secret key","signature":"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c"}`)
var m1 macaroon.Macaroon
err = m1.UnmarshalJSON(jsonData)
c.Assert(err, gc.IsNil)
assertEqualMacaroons(c, &m0, &m1)
}
var binaryFieldBase64ChoiceTests = []struct {
id string
expectBase64 bool
}{
{"x", false},
{"\x00", true},
{"\x03\x00", true},
{"a longer id with more stuff", false},
{"a longer id with more stuff and one invalid \xff", true},
{"a longer id with more stuff and one encoded \x00", false},
}
func (*macaroonSuite) TestBinaryFieldBase64Choice(c *gc.C) {
for i, test := range binaryFieldBase64ChoiceTests {
c.Logf("test %d: %q", i, test.id)
m := MustNew([]byte{0}, []byte(test.id), "", macaroon.LatestVersion)
data, err := json.Marshal(m)
c.Assert(err, gc.Equals, nil)
var x struct {
Id *string `json:"i"`
Id64 *string `json:"i64"`
}
err = json.Unmarshal(data, &x)
c.Assert(err, gc.Equals, nil)
if test.expectBase64 {
c.Assert(x.Id64, gc.NotNil)
c.Assert(x.Id, gc.IsNil)
idDec, err := base64.RawURLEncoding.DecodeString(*x.Id64)
c.Assert(err, gc.Equals, nil)
c.Assert(string(idDec), gc.Equals, test.id)
} else {
c.Assert(x.Id64, gc.IsNil)
c.Assert(x.Id, gc.NotNil)
c.Assert(*x.Id, gc.Equals, test.id)
}
}
}

View file

@ -0,0 +1,190 @@
package macaroon
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"unicode/utf8"
)
// macaroonJSONV1 defines the V1 JSON format for macaroons.
type macaroonJSONV1 struct {
Caveats []caveatJSONV1 `json:"caveats"`
Location string `json:"location"`
Identifier string `json:"identifier"`
Signature string `json:"signature"` // hex-encoded
}
// caveatJSONV1 defines the V1 JSON format for caveats within a macaroon.
type caveatJSONV1 struct {
CID string `json:"cid"`
VID string `json:"vid,omitempty"`
Location string `json:"cl,omitempty"`
}
// marshalJSONV1 marshals the macaroon to the V1 JSON format.
func (m *Macaroon) marshalJSONV1() ([]byte, error) {
if !utf8.Valid(m.id) {
return nil, fmt.Errorf("macaroon id is not valid UTF-8")
}
mjson := macaroonJSONV1{
Location: m.location,
Identifier: string(m.id),
Signature: hex.EncodeToString(m.sig[:]),
Caveats: make([]caveatJSONV1, len(m.caveats)),
}
for i, cav := range m.caveats {
if !utf8.Valid(cav.Id) {
return nil, fmt.Errorf("caveat id is not valid UTF-8")
}
mjson.Caveats[i] = caveatJSONV1{
Location: cav.Location,
CID: string(cav.Id),
VID: base64.RawURLEncoding.EncodeToString(cav.VerificationId),
}
}
data, err := json.Marshal(mjson)
if err != nil {
return nil, fmt.Errorf("cannot marshal json data: %v", err)
}
return data, nil
}
// initJSONV1 initializes m from the JSON-unmarshaled data
// held in mjson.
func (m *Macaroon) initJSONV1(mjson *macaroonJSONV1) error {
m.init([]byte(mjson.Identifier), mjson.Location, V1)
sig, err := hex.DecodeString(mjson.Signature)
if err != nil {
return fmt.Errorf("cannot decode macaroon signature %q: %v", m.sig, err)
}
if len(sig) != hashLen {
return fmt.Errorf("signature has unexpected length %d", len(sig))
}
copy(m.sig[:], sig)
m.caveats = m.caveats[:0]
for _, cav := range mjson.Caveats {
vid, err := Base64Decode([]byte(cav.VID))
if err != nil {
return fmt.Errorf("cannot decode verification id %q: %v", cav.VID, err)
}
m.appendCaveat([]byte(cav.CID), vid, cav.Location)
}
return nil
}
// The original (v1) binary format of a macaroon is as follows.
// Each identifier represents a v1 packet.
//
// location
// identifier
// (
// caveatId?
// verificationId?
// caveatLocation?
// )*
// signature
// parseBinaryV1 parses the given data in V1 format into the macaroon. The macaroon's
// internal data structures will retain references to the data. It
// returns the data after the end of the macaroon.
func (m *Macaroon) parseBinaryV1(data []byte) ([]byte, error) {
var err error
loc, err := expectPacketV1(data, fieldNameLocation)
if err != nil {
return nil, err
}
data = data[loc.totalLen:]
id, err := expectPacketV1(data, fieldNameIdentifier)
if err != nil {
return nil, err
}
data = data[id.totalLen:]
m.init(id.data, string(loc.data), V1)
var cav Caveat
for {
p, err := parsePacketV1(data)
if err != nil {
return nil, err
}
data = data[p.totalLen:]
switch field := string(p.fieldName); field {
case fieldNameSignature:
// At the end of the caveats we find the signature.
if cav.Id != nil {
m.caveats = append(m.caveats, cav)
}
if len(p.data) != hashLen {
return nil, fmt.Errorf("signature has unexpected length %d", len(p.data))
}
copy(m.sig[:], p.data)
return data, nil
case fieldNameCaveatId:
if cav.Id != nil {
m.caveats = append(m.caveats, cav)
cav = Caveat{}
}
cav.Id = p.data
case fieldNameVerificationId:
if cav.VerificationId != nil {
return nil, fmt.Errorf("repeated field %q in caveat", fieldNameVerificationId)
}
cav.VerificationId = p.data
case fieldNameCaveatLocation:
if cav.Location != "" {
return nil, fmt.Errorf("repeated field %q in caveat", fieldNameLocation)
}
cav.Location = string(p.data)
default:
return nil, fmt.Errorf("unexpected field %q", field)
}
}
}
func expectPacketV1(data []byte, kind string) (packetV1, error) {
p, err := parsePacketV1(data)
if err != nil {
return packetV1{}, err
}
if field := string(p.fieldName); field != kind {
return packetV1{}, fmt.Errorf("unexpected field %q; expected %s", field, kind)
}
return p, nil
}
// appendBinaryV1 appends the binary encoding of m to data.
func (m *Macaroon) appendBinaryV1(data []byte) ([]byte, error) {
var ok bool
data, ok = appendPacketV1(data, fieldNameLocation, []byte(m.location))
if !ok {
return nil, fmt.Errorf("failed to append location to macaroon, packet is too long")
}
data, ok = appendPacketV1(data, fieldNameIdentifier, m.id)
if !ok {
return nil, fmt.Errorf("failed to append identifier to macaroon, packet is too long")
}
for _, cav := range m.caveats {
data, ok = appendPacketV1(data, fieldNameCaveatId, cav.Id)
if !ok {
return nil, fmt.Errorf("failed to append caveat id to macaroon, packet is too long")
}
if cav.VerificationId == nil {
continue
}
data, ok = appendPacketV1(data, fieldNameVerificationId, cav.VerificationId)
if !ok {
return nil, fmt.Errorf("failed to append verification id to macaroon, packet is too long")
}
data, ok = appendPacketV1(data, fieldNameCaveatLocation, []byte(cav.Location))
if !ok {
return nil, fmt.Errorf("failed to append verification id to macaroon, packet is too long")
}
}
data, ok = appendPacketV1(data, fieldNameSignature, m.sig[:])
if !ok {
return nil, fmt.Errorf("failed to append signature to macaroon, packet is too long")
}
return data, nil
}

View file

@ -0,0 +1,253 @@
package macaroon
import (
"encoding/base64"
"encoding/json"
"fmt"
"unicode/utf8"
)
// macaroonJSONV2 defines the V2 JSON format for macaroons.
type macaroonJSONV2 struct {
Caveats []caveatJSONV2 `json:"c,omitempty"`
Location string `json:"l,omitempty"`
Identifier string `json:"i,omitempty"`
Identifier64 string `json:"i64,omitempty"`
Signature string `json:"s,omitempty"`
Signature64 string `json:"s64,omitempty"`
}
// caveatJSONV2 defines the V2 JSON format for caveats within a macaroon.
type caveatJSONV2 struct {
CID string `json:"i,omitempty"`
CID64 string `json:"i64,omitempty"`
VID string `json:"v,omitempty"`
VID64 string `json:"v64,omitempty"`
Location string `json:"l,omitempty"`
}
func (m *Macaroon) marshalJSONV2() ([]byte, error) {
mjson := macaroonJSONV2{
Location: m.location,
Caveats: make([]caveatJSONV2, len(m.caveats)),
}
putJSONBinaryField(m.id, &mjson.Identifier, &mjson.Identifier64)
putJSONBinaryField(m.sig[:], &mjson.Signature, &mjson.Signature64)
for i, cav := range m.caveats {
cavjson := caveatJSONV2{
Location: cav.Location,
}
putJSONBinaryField(cav.Id, &cavjson.CID, &cavjson.CID64)
putJSONBinaryField(cav.VerificationId, &cavjson.VID, &cavjson.VID64)
mjson.Caveats[i] = cavjson
}
data, err := json.Marshal(mjson)
if err != nil {
return nil, fmt.Errorf("cannot marshal json data: %v", err)
}
return data, nil
}
// initJSONV2 initializes m from the JSON-unmarshaled data
// held in mjson.
func (m *Macaroon) initJSONV2(mjson *macaroonJSONV2) error {
id, err := jsonBinaryField(mjson.Identifier, mjson.Identifier64)
if err != nil {
return fmt.Errorf("invalid identifier: %v", err)
}
m.init(id, mjson.Location, V2)
sig, err := jsonBinaryField(mjson.Signature, mjson.Signature64)
if err != nil {
return fmt.Errorf("invalid signature: %v", err)
}
if len(sig) != hashLen {
return fmt.Errorf("signature has unexpected length %d", len(sig))
}
copy(m.sig[:], sig)
m.caveats = make([]Caveat, 0, len(mjson.Caveats))
for _, cav := range mjson.Caveats {
cid, err := jsonBinaryField(cav.CID, cav.CID64)
if err != nil {
return fmt.Errorf("invalid cid in caveat: %v", err)
}
vid, err := jsonBinaryField(cav.VID, cav.VID64)
if err != nil {
return fmt.Errorf("invalid vid in caveat: %v", err)
}
m.appendCaveat(cid, vid, cav.Location)
}
return nil
}
// putJSONBinaryField puts the value of x into one
// of the appropriate fields depending on its value.
func putJSONBinaryField(x []byte, s, sb64 *string) {
if !utf8.Valid(x) {
*sb64 = base64.RawURLEncoding.EncodeToString(x)
return
}
// We could use either string or base64 encoding;
// choose the most compact of the two possibilities.
b64len := base64.RawURLEncoding.EncodedLen(len(x))
sx := string(x)
if jsonEnc, _ := json.Marshal(sx); len(jsonEnc)-2 <= b64len+2 {
// The JSON encoding is smaller than the base 64 encoding.
// NB marshaling a string can never return an error;
// it always includes the two quote characters;
// but using base64 also uses two extra characters for the
// "64" suffix on the field name. If all is equal, prefer string
// encoding because it's more readable.
*s = sx
return
}
*sb64 = base64.RawURLEncoding.EncodeToString(x)
}
// jsonBinaryField returns the value of a JSON field that may
// be string, hex or base64-encoded.
func jsonBinaryField(s, sb64 string) ([]byte, error) {
switch {
case s != "":
if sb64 != "" {
return nil, fmt.Errorf("ambiguous field encoding")
}
return []byte(s), nil
case sb64 != "":
return Base64Decode([]byte(sb64))
}
return []byte{}, nil
}
// The v2 binary format of a macaroon is as follows.
// All entries other than the version are packets as
// parsed by parsePacketV2.
//
// version [1 byte]
// location?
// identifier
// eos
// (
// location?
// identifier
// verificationId?
// eos
// )*
// eos
// signature
//
// See also https://github.com/rescrv/libmacaroons/blob/master/doc/format.txt
// parseBinaryV2 parses the given data in V2 format into the macaroon. The macaroon's
// internal data structures will retain references to the data. It
// returns the data after the end of the macaroon.
func (m *Macaroon) parseBinaryV2(data []byte) ([]byte, error) {
// The version has already been checked, so
// skip it.
data = data[1:]
data, section, err := parseSectionV2(data)
if err != nil {
return nil, err
}
var loc string
if len(section) > 0 && section[0].fieldType == fieldLocation {
loc = string(section[0].data)
section = section[1:]
}
if len(section) != 1 || section[0].fieldType != fieldIdentifier {
return nil, fmt.Errorf("invalid macaroon header")
}
id := section[0].data
m.init(id, loc, V2)
for {
rest, section, err := parseSectionV2(data)
if err != nil {
return nil, err
}
data = rest
if len(section) == 0 {
break
}
var cav Caveat
if len(section) > 0 && section[0].fieldType == fieldLocation {
cav.Location = string(section[0].data)
section = section[1:]
}
if len(section) == 0 || section[0].fieldType != fieldIdentifier {
return nil, fmt.Errorf("no identifier in caveat")
}
cav.Id = section[0].data
section = section[1:]
if len(section) == 0 {
// First party caveat.
if cav.Location != "" {
return nil, fmt.Errorf("location not allowed in first party caveat")
}
m.caveats = append(m.caveats, cav)
continue
}
if len(section) != 1 {
return nil, fmt.Errorf("extra fields found in caveat")
}
if section[0].fieldType != fieldVerificationId {
return nil, fmt.Errorf("invalid field found in caveat")
}
cav.VerificationId = section[0].data
m.caveats = append(m.caveats, cav)
}
data, sig, err := parsePacketV2(data)
if err != nil {
return nil, err
}
if sig.fieldType != fieldSignature {
return nil, fmt.Errorf("unexpected field found instead of signature")
}
if len(sig.data) != hashLen {
return nil, fmt.Errorf("signature has unexpected length")
}
copy(m.sig[:], sig.data)
return data, nil
}
// appendBinaryV2 appends the binary-encoded macaroon
// in v2 format to data.
func (m *Macaroon) appendBinaryV2(data []byte) []byte {
// Version byte.
data = append(data, 2)
if len(m.location) > 0 {
data = appendPacketV2(data, packetV2{
fieldType: fieldLocation,
data: []byte(m.location),
})
}
data = appendPacketV2(data, packetV2{
fieldType: fieldIdentifier,
data: m.id,
})
data = appendEOSV2(data)
for _, cav := range m.caveats {
if len(cav.Location) > 0 {
data = appendPacketV2(data, packetV2{
fieldType: fieldLocation,
data: []byte(cav.Location),
})
}
data = appendPacketV2(data, packetV2{
fieldType: fieldIdentifier,
data: cav.Id,
})
if len(cav.VerificationId) > 0 {
data = appendPacketV2(data, packetV2{
fieldType: fieldVerificationId,
data: []byte(cav.VerificationId),
})
}
data = appendEOSV2(data)
}
data = appendEOSV2(data)
data = appendPacketV2(data, packetV2{
fieldType: fieldSignature,
data: m.sig[:],
})
return data
}

View file

@ -0,0 +1,239 @@
package macaroon
import (
"encoding/base64"
"encoding/json"
"fmt"
)
// Version specifies the version of a macaroon.
// In version 1, the macaroon id and all caveats
// must be UTF-8-compatible strings, and the
// size of any part of the macaroon may not exceed
// approximately 64K. In version 2,
// all field may be arbitrary binary blobs.
type Version uint16
const (
// V1 specifies version 1 macaroons.
V1 Version = 1
// V2 specifies version 2 macaroons.
V2 Version = 2
// LatestVersion holds the latest supported version.
LatestVersion = V2
)
// String returns a string representation of the version;
// for example V1 formats as "v1".
func (v Version) String() string {
return fmt.Sprintf("v%d", v)
}
// Version returns the version of the macaroon.
func (m *Macaroon) Version() Version {
return m.version
}
// MarshalJSON implements json.Marshaler by marshaling the
// macaroon in JSON format. The serialisation format is determined
// by the macaroon's version.
func (m *Macaroon) MarshalJSON() ([]byte, error) {
switch m.version {
case V1:
return m.marshalJSONV1()
case V2:
return m.marshalJSONV2()
default:
return nil, fmt.Errorf("unknown version %v", m.version)
}
}
// UnmarshalJSON implements json.Unmarshaller by unmarshaling
// the given macaroon in JSON format. It accepts both V1 and V2
// forms encoded forms, and also a base64-encoded JSON string
// containing the binary-marshaled macaroon.
//
// After unmarshaling, the macaroon's version will reflect
// the version that it was unmarshaled as.
func (m *Macaroon) UnmarshalJSON(data []byte) error {
if data[0] == '"' {
// It's a string, so it must be a base64-encoded binary form.
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
data, err := Base64Decode([]byte(s))
if err != nil {
return err
}
if err := m.UnmarshalBinary(data); err != nil {
return err
}
return nil
}
// Not a string; try to unmarshal into both kinds of macaroon object.
// This assumes that neither format has any fields in common.
// For subsequent versions we may need to change this approach.
type MacaroonJSONV1 macaroonJSONV1
type MacaroonJSONV2 macaroonJSONV2
var both struct {
*MacaroonJSONV1
*MacaroonJSONV2
}
if err := json.Unmarshal(data, &both); err != nil {
return err
}
switch {
case both.MacaroonJSONV1 != nil && both.MacaroonJSONV2 != nil:
return fmt.Errorf("cannot determine macaroon encoding version")
case both.MacaroonJSONV1 != nil:
if err := m.initJSONV1((*macaroonJSONV1)(both.MacaroonJSONV1)); err != nil {
return err
}
m.version = V1
case both.MacaroonJSONV2 != nil:
if err := m.initJSONV2((*macaroonJSONV2)(both.MacaroonJSONV2)); err != nil {
return err
}
m.version = V2
default:
return fmt.Errorf("invalid JSON macaroon encoding")
}
return nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
// It accepts both V1 and V2 binary encodings.
func (m *Macaroon) UnmarshalBinary(data []byte) error {
// Copy the data to avoid retaining references to it
// in the internal data structures.
data = append([]byte(nil), data...)
_, err := m.parseBinary(data)
return err
}
// parseBinary parses the macaroon in binary format
// from the given data and returns where the parsed data ends.
//
// It retains references to data.
func (m *Macaroon) parseBinary(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty macaroon data")
}
v := data[0]
if v == 2 {
// Version 2 binary format.
data, err := m.parseBinaryV2(data)
if err != nil {
return nil, fmt.Errorf("unmarshal v2: %v", err)
}
m.version = V2
return data, nil
}
if isASCIIHex(v) {
// It's a hex digit - version 1 binary format
data, err := m.parseBinaryV1(data)
if err != nil {
return nil, fmt.Errorf("unmarshal v1: %v", err)
}
m.version = V1
return data, nil
}
return nil, fmt.Errorf("cannot determine data format of binary-encoded macaroon")
}
// MarshalBinary implements encoding.BinaryMarshaler by
// formatting the macaroon according to the version specified
// by MarshalAs.
func (m *Macaroon) MarshalBinary() ([]byte, error) {
return m.appendBinary(nil)
}
// appendBinary appends the binary-formatted macaroon to
// the given data, formatting it according to the macaroon's
// version.
func (m *Macaroon) appendBinary(data []byte) ([]byte, error) {
switch m.version {
case V1:
return m.appendBinaryV1(data)
case V2:
return m.appendBinaryV2(data), nil
default:
return nil, fmt.Errorf("bad macaroon version %v", m.version)
}
}
// Slice defines a collection of macaroons. By convention, the
// first macaroon in the slice is a primary macaroon and the rest
// are discharges for its third party caveats.
type Slice []*Macaroon
// MarshalBinary implements encoding.BinaryMarshaler.
func (s Slice) MarshalBinary() ([]byte, error) {
var data []byte
var err error
for _, m := range s {
data, err = m.appendBinary(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal macaroon %q: %v", m.Id(), err)
}
}
return data, nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
// It accepts all known binary encodings for the data - all the
// embedded macaroons need not be encoded in the same format.
func (s *Slice) UnmarshalBinary(data []byte) error {
// Prevent the internal data structures from holding onto the
// slice by copying it first.
data = append([]byte(nil), data...)
*s = (*s)[:0]
for len(data) > 0 {
var m Macaroon
rest, err := m.parseBinary(data)
if err != nil {
return fmt.Errorf("cannot unmarshal macaroon: %v", err)
}
*s = append(*s, &m)
data = rest
}
return nil
}
const (
padded = 1 << iota
stdEncoding
)
var codecs = [4]*base64.Encoding{
0: base64.RawURLEncoding,
padded: base64.URLEncoding,
stdEncoding: base64.RawStdEncoding,
stdEncoding | padded: base64.StdEncoding,
}
// Base64Decode base64-decodes the given data.
// It accepts both standard and URL encodings, both
// padded and unpadded.
func Base64Decode(data []byte) ([]byte, error) {
encoding := 0
if len(data) > 0 && data[len(data)-1] == '=' {
encoding |= padded
}
for _, b := range data {
if b == '/' || b == '+' {
encoding |= stdEncoding
break
}
}
codec := codecs[encoding]
buf := make([]byte, codec.DecodedLen(len(data)))
n, err := codec.Decode(buf, data)
if err == nil {
return buf[0:n], nil
}
return nil, err
}

View file

@ -0,0 +1,203 @@
package macaroon_test
import (
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"gopkg.in/macaroon.v2"
)
type marshalSuite struct{}
var _ = gc.Suite(&marshalSuite{})
func (s *marshalSuite) TestMarshalUnmarshalMacaroonV1(c *gc.C) {
s.testMarshalUnmarshalWithVersion(c, macaroon.V1)
}
func (s *marshalSuite) TestMarshalUnmarshalMacaroonV2(c *gc.C) {
s.testMarshalUnmarshalWithVersion(c, macaroon.V2)
}
func (*marshalSuite) testMarshalUnmarshalWithVersion(c *gc.C, vers macaroon.Version) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", vers)
// Adding the third party caveat before the first party caveat
// tests a former bug where the caveat wasn't zeroed
// before moving to the next caveat.
err := m.AddThirdPartyCaveat([]byte("shared root key"), []byte("3rd party caveat"), "remote.com")
c.Assert(err, gc.IsNil)
err = m.AddFirstPartyCaveat([]byte("a caveat"))
c.Assert(err, gc.IsNil)
b, err := m.MarshalBinary()
c.Assert(err, gc.IsNil)
var um macaroon.Macaroon
err = um.UnmarshalBinary(b)
c.Assert(err, gc.IsNil)
c.Assert(um.Location(), gc.Equals, m.Location())
c.Assert(string(um.Id()), gc.Equals, string(m.Id()))
c.Assert(um.Signature(), jc.DeepEquals, m.Signature())
c.Assert(um.Caveats(), jc.DeepEquals, m.Caveats())
c.Assert(um.Version(), gc.Equals, vers)
um.SetVersion(m.Version())
c.Assert(m, jc.DeepEquals, &um)
}
func (s *marshalSuite) TestMarshalBinaryRoundTrip(c *gc.C) {
// This data holds the V2 binary encoding of
data := []byte(
"\x02" +
"\x01\x0ehttp://mybank/" +
"\x02\x1cwe used our other secret key" +
"\x00" +
"\x02\x14account = 3735928559" +
"\x00" +
"\x01\x13http://auth.mybank/" +
"\x02'this was how we remind auth of key/pred" +
"\x04\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x6e\xc5\x02\xe0\x58\x86\xd1\xf0\x27\x9f\x05\x5f\xa5\x25\x54\xd1\x6d\x16\xc1\xb1\x40\x74\xbb\xb8\x3f\xf0\xfd\xd7\x9d\xc2\xfe\x09\x8f\x0e\xd4\xa2\xb0\x91\x13\x0e\x6b\x5d\xb4\x6a\x20\xa8\x6b" +
"\x00" +
"\x00" +
"\x06\x20\xd2\x7d\xb2\xfd\x1f\x22\x76\x0e\x4c\x3d\xae\x81\x37\xe2\xd8\xfc\x1d\xf6\xc0\x74\x1c\x18\xae\xd4\xb9\x72\x56\xbf\x78\xd1\xf5\x5c",
)
var m macaroon.Macaroon
err := m.UnmarshalBinary(data)
c.Assert(err, gc.Equals, nil)
assertLibMacaroonsMacaroon(c, &m)
c.Assert(m.Version(), gc.Equals, macaroon.V2)
data1, err := m.MarshalBinary()
c.Assert(err, gc.Equals, nil)
c.Assert(data1, jc.DeepEquals, data)
}
func (s *marshalSuite) TestMarshalUnmarshalSliceV1(c *gc.C) {
s.testMarshalUnmarshalSliceWithVersion(c, macaroon.V1)
}
func (s *marshalSuite) TestMarshalUnmarshalSliceV2(c *gc.C) {
s.testMarshalUnmarshalSliceWithVersion(c, macaroon.V2)
}
func (*marshalSuite) testMarshalUnmarshalSliceWithVersion(c *gc.C, vers macaroon.Version) {
rootKey := []byte("secret")
m1 := MustNew(rootKey, []byte("some id"), "a location", vers)
m2 := MustNew(rootKey, []byte("some other id"), "another location", vers)
err := m1.AddFirstPartyCaveat([]byte("a caveat"))
c.Assert(err, gc.IsNil)
err = m2.AddFirstPartyCaveat([]byte("another caveat"))
c.Assert(err, gc.IsNil)
macaroons := macaroon.Slice{m1, m2}
b, err := macaroons.MarshalBinary()
c.Assert(err, gc.IsNil)
var unmarshaledMacs macaroon.Slice
err = unmarshaledMacs.UnmarshalBinary(b)
c.Assert(err, gc.IsNil)
c.Assert(unmarshaledMacs, gc.HasLen, len(macaroons))
for i, m := range macaroons {
um := unmarshaledMacs[i]
c.Assert(um.Location(), gc.Equals, m.Location())
c.Assert(string(um.Id()), gc.Equals, string(m.Id()))
c.Assert(um.Signature(), jc.DeepEquals, m.Signature())
c.Assert(um.Caveats(), jc.DeepEquals, m.Caveats())
c.Assert(um.Version(), gc.Equals, vers)
um.SetVersion(m.Version())
}
c.Assert(macaroons, jc.DeepEquals, unmarshaledMacs)
// Check that appending a caveat to the first does not
// affect the second.
for i := 0; i < 10; i++ {
err = unmarshaledMacs[0].AddFirstPartyCaveat([]byte("caveat"))
c.Assert(err, gc.IsNil)
}
unmarshaledMacs[1].SetVersion(macaroons[1].Version())
c.Assert(unmarshaledMacs[1], jc.DeepEquals, macaroons[1])
c.Assert(err, gc.IsNil)
}
func (s *marshalSuite) TestSliceRoundTripV1(c *gc.C) {
s.testSliceRoundTripWithVersion(c, macaroon.V1)
}
func (s *marshalSuite) TestSliceRoundTripV2(c *gc.C) {
s.testSliceRoundTripWithVersion(c, macaroon.V2)
}
func (*marshalSuite) testSliceRoundTripWithVersion(c *gc.C, vers macaroon.Version) {
rootKey := []byte("secret")
m1 := MustNew(rootKey, []byte("some id"), "a location", vers)
m2 := MustNew(rootKey, []byte("some other id"), "another location", vers)
err := m1.AddFirstPartyCaveat([]byte("a caveat"))
c.Assert(err, gc.IsNil)
err = m2.AddFirstPartyCaveat([]byte("another caveat"))
c.Assert(err, gc.IsNil)
macaroons := macaroon.Slice{m1, m2}
b, err := macaroons.MarshalBinary()
c.Assert(err, gc.IsNil)
var unmarshaledMacs macaroon.Slice
err = unmarshaledMacs.UnmarshalBinary(b)
c.Assert(err, gc.IsNil)
marshaledMacs, err := unmarshaledMacs.MarshalBinary()
c.Assert(err, gc.IsNil)
c.Assert(b, jc.DeepEquals, marshaledMacs)
}
var base64DecodeTests = []struct {
about string
input string
expect string
expectError string
}{{
about: "empty string",
input: "",
expect: "",
}, {
about: "standard encoding, padded",
input: "Z29+IQ==",
expect: "go~!",
}, {
about: "URL encoding, padded",
input: "Z29-IQ==",
expect: "go~!",
}, {
about: "standard encoding, not padded",
input: "Z29+IQ",
expect: "go~!",
}, {
about: "URL encoding, not padded",
input: "Z29-IQ",
expect: "go~!",
}, {
about: "standard encoding, too much padding",
input: "Z29+IQ===",
expectError: `illegal base64 data at input byte 8`,
}}
func (*marshalSuite) TestBase64Decode(c *gc.C) {
for i, test := range base64DecodeTests {
c.Logf("test %d: %s", i, test.about)
out, err := macaroon.Base64Decode([]byte(test.input))
if test.expectError != "" {
c.Assert(err, gc.ErrorMatches, test.expectError)
} else {
c.Assert(err, gc.Equals, nil)
c.Assert(string(out), gc.Equals, test.expect)
}
}
}

View file

@ -0,0 +1,133 @@
package macaroon
import (
"bytes"
"fmt"
)
// field names, as defined in libmacaroons
const (
fieldNameLocation = "location"
fieldNameIdentifier = "identifier"
fieldNameSignature = "signature"
fieldNameCaveatId = "cid"
fieldNameVerificationId = "vid"
fieldNameCaveatLocation = "cl"
)
// maxPacketV1Len is the maximum allowed length of a packet in the v1 macaroon
// serialization format.
const maxPacketV1Len = 0xffff
// The original macaroon binary encoding is made from a sequence
// of "packets", each of which has a field name and some data.
// The encoding is:
//
// - four ascii hex digits holding the entire packet size (including
// the digits themselves).
//
// - the field name, followed by an ascii space.
//
// - the raw data
//
// - a newline (\n) character
//
// The packet struct below holds a reference into Macaroon.data.
type packetV1 struct {
// ftype holds the field name of the packet.
fieldName []byte
// data holds the packet's data.
data []byte
// len holds the total length in bytes
// of the packet, including any header.
totalLen int
}
// parsePacket parses the packet at the start of the
// given data.
func parsePacketV1(data []byte) (packetV1, error) {
if len(data) < 6 {
return packetV1{}, fmt.Errorf("packet too short")
}
plen, ok := parseSizeV1(data)
if !ok {
return packetV1{}, fmt.Errorf("cannot parse size")
}
if plen > len(data) {
return packetV1{}, fmt.Errorf("packet size too big")
}
if plen < 4 {
return packetV1{}, fmt.Errorf("packet size too small")
}
data = data[4:plen]
i := bytes.IndexByte(data, ' ')
if i <= 0 {
return packetV1{}, fmt.Errorf("cannot parse field name")
}
fieldName := data[0:i]
if data[len(data)-1] != '\n' {
return packetV1{}, fmt.Errorf("no terminating newline found")
}
return packetV1{
fieldName: fieldName,
data: data[i+1 : len(data)-1],
totalLen: plen,
}, nil
}
// appendPacketV1 appends a packet with the given field name
// and data to the given buffer. If the field and data were
// too long to be encoded, it returns nil, false; otherwise
// it returns the appended buffer.
func appendPacketV1(buf []byte, field string, data []byte) ([]byte, bool) {
plen := packetV1Size(field, data)
if plen > maxPacketV1Len {
return nil, false
}
buf = appendSizeV1(buf, plen)
buf = append(buf, field...)
buf = append(buf, ' ')
buf = append(buf, data...)
buf = append(buf, '\n')
return buf, true
}
func packetV1Size(field string, data []byte) int {
return 4 + len(field) + 1 + len(data) + 1
}
var hexDigits = []byte("0123456789abcdef")
func appendSizeV1(data []byte, size int) []byte {
return append(data,
hexDigits[size>>12],
hexDigits[(size>>8)&0xf],
hexDigits[(size>>4)&0xf],
hexDigits[size&0xf],
)
}
func parseSizeV1(data []byte) (int, bool) {
d0, ok0 := asciiHex(data[0])
d1, ok1 := asciiHex(data[1])
d2, ok2 := asciiHex(data[2])
d3, ok3 := asciiHex(data[3])
return d0<<12 + d1<<8 + d2<<4 + d3, ok0 && ok1 && ok2 && ok3
}
func asciiHex(b byte) (int, bool) {
switch {
case b >= '0' && b <= '9':
return int(b) - '0', true
case b >= 'a' && b <= 'f':
return int(b) - 'a' + 0xa, true
}
return 0, false
}
func isASCIIHex(b byte) bool {
_, ok := asciiHex(b)
return ok
}

View file

@ -0,0 +1,98 @@
package macaroon
import (
"strconv"
"strings"
"unicode"
gc "gopkg.in/check.v1"
)
type packetV1Suite struct{}
var _ = gc.Suite(&packetV1Suite{})
func (*packetV1Suite) TestAppendPacket(c *gc.C) {
data, ok := appendPacketV1(nil, "field", []byte("some data"))
c.Assert(ok, gc.Equals, true)
c.Assert(string(data), gc.Equals, "0014field some data\n")
data, ok = appendPacketV1(data, "otherfield", []byte("more and more data"))
c.Assert(ok, gc.Equals, true)
c.Assert(string(data), gc.Equals, "0014field some data\n0022otherfield more and more data\n")
}
func (*packetV1Suite) TestAppendPacketTooBig(c *gc.C) {
data, ok := appendPacketV1(nil, "field", make([]byte, 65532))
c.Assert(ok, gc.Equals, false)
c.Assert(data, gc.IsNil)
}
var parsePacketV1Tests = []struct {
data string
expect packetV1
expectErr string
}{{
expectErr: "packet too short",
}, {
data: "0014field some data\n",
expect: packetV1{
fieldName: []byte("field"),
data: []byte("some data"),
totalLen: 20,
},
}, {
data: "0015field some data\n",
expectErr: "packet size too big",
}, {
data: "0003a\n",
expectErr: "packet size too small",
}, {
data: "0014fieldwithoutanyspaceordata\n",
expectErr: "cannot parse field name",
}, {
data: "fedcsomefield " + strings.Repeat("x", 0xfedc-len("0000somefield \n")) + "\n",
expect: packetV1{
fieldName: []byte("somefield"),
data: []byte(strings.Repeat("x", 0xfedc-len("0000somefield \n"))),
totalLen: 0xfedc,
},
}, {
data: "zzzzbadpacketsizenomacaroon",
expectErr: "cannot parse size",
}}
func (*packetV1Suite) TestParsePacketV1(c *gc.C) {
for i, test := range parsePacketV1Tests {
c.Logf("test %d: %q", i, truncate(test.data))
p, err := parsePacketV1([]byte(test.data))
if test.expectErr != "" {
c.Assert(err, gc.ErrorMatches, test.expectErr)
c.Assert(p, gc.DeepEquals, packetV1{})
continue
}
c.Assert(err, gc.IsNil)
c.Assert(p, gc.DeepEquals, test.expect)
}
}
func truncate(d string) string {
if len(d) > 50 {
return d[0:50] + "..."
}
return d
}
func (*packetV1Suite) TestAsciiHex(c *gc.C) {
for b := 0; b < 256; b++ {
n, err := strconv.ParseInt(string(b), 16, 8)
value, ok := asciiHex(byte(b))
if err != nil || unicode.IsUpper(rune(b)) {
c.Assert(ok, gc.Equals, false)
c.Assert(value, gc.Equals, 0)
} else {
c.Assert(ok, gc.Equals, true)
c.Assert(value, gc.Equals, int(n))
}
}
}

View file

@ -0,0 +1,117 @@
package macaroon
import (
"encoding/binary"
"fmt"
)
type fieldType int
// Field constants as used in the binary encoding.
const (
fieldEOS fieldType = 0
fieldLocation fieldType = 1
fieldIdentifier fieldType = 2
fieldVerificationId fieldType = 4
fieldSignature fieldType = 6
)
type packetV2 struct {
// fieldType holds the type of the field.
fieldType fieldType
// data holds the packet's data.
data []byte
}
// parseSectionV2 parses a sequence of packets
// in data. The sequence is terminated by a packet
// with a field type of fieldEOS.
func parseSectionV2(data []byte) ([]byte, []packetV2, error) {
prevFieldType := fieldType(-1)
var packets []packetV2
for {
if len(data) == 0 {
return nil, nil, fmt.Errorf("section extends past end of buffer")
}
rest, p, err := parsePacketV2(data)
if err != nil {
return nil, nil, err
}
if p.fieldType == fieldEOS {
return rest, packets, nil
}
if p.fieldType <= prevFieldType {
return nil, nil, fmt.Errorf("fields out of order")
}
packets = append(packets, p)
prevFieldType = p.fieldType
data = rest
}
}
// parsePacketV2 parses a V2 data package at the start
// of the given data.
// The format of a packet is as follows:
//
// fieldType(varint) payloadLen(varint) data[payloadLen bytes]
//
// apart from fieldEOS which has no payloadLen or data (it's
// a single zero byte).
func parsePacketV2(data []byte) ([]byte, packetV2, error) {
data, ft, err := parseVarint(data)
if err != nil {
return nil, packetV2{}, err
}
p := packetV2{
fieldType: fieldType(ft),
}
if p.fieldType == fieldEOS {
return data, p, nil
}
data, payloadLen, err := parseVarint(data)
if err != nil {
return nil, packetV2{}, err
}
if payloadLen > len(data) {
return nil, packetV2{}, fmt.Errorf("field data extends past end of buffer")
}
p.data = data[0:payloadLen]
return data[payloadLen:], p, nil
}
// parseVarint parses the variable-length integer
// at the start of the given data and returns rest
// of the buffer and the number.
func parseVarint(data []byte) ([]byte, int, error) {
val, n := binary.Uvarint(data)
if n > 0 {
if val > 0x7fffffff {
return nil, 0, fmt.Errorf("varint value out of range")
}
return data[n:], int(val), nil
}
if n == 0 {
return nil, 0, fmt.Errorf("varint value extends past end of buffer")
}
return nil, 0, fmt.Errorf("varint value out of range")
}
func appendPacketV2(data []byte, p packetV2) []byte {
data = appendVarint(data, int(p.fieldType))
if p.fieldType != fieldEOS {
data = appendVarint(data, len(p.data))
data = append(data, p.data...)
}
return data
}
func appendEOSV2(data []byte) []byte {
return append(data, 0)
}
func appendVarint(data []byte, x int) []byte {
var buf [binary.MaxVarintLen32]byte
n := binary.PutUvarint(buf[:], uint64(x))
return append(data, buf[:n]...)
}

View file

@ -0,0 +1,126 @@
package macaroon
import (
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)
type packetV2Suite struct{}
var _ = gc.Suite(&packetV2Suite{})
var parsePacketV2Tests = []struct {
about string
data string
expectPacket packetV2
expectData string
expectError string
}{{
about: "EOS packet",
data: "\x00",
expectPacket: packetV2{
fieldType: fieldEOS,
},
}, {
about: "simple field",
data: "\x02\x03xyz",
expectPacket: packetV2{
fieldType: 2,
data: []byte("xyz"),
},
}, {
about: "empty buffer",
data: "",
expectError: "varint value extends past end of buffer",
}, {
about: "varint out of range",
data: "\xff\xff\xff\xff\xff\xff\x7f",
expectError: "varint value out of range",
}, {
about: "varint way out of range",
data: "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f",
expectError: "varint value out of range",
}, {
about: "unterminated varint",
data: "\x80",
expectError: "varint value extends past end of buffer",
}, {
about: "field data too long",
data: "\x01\x02a",
expectError: "field data extends past end of buffer",
}, {
about: "bad data length varint",
data: "\x01\xff",
expectError: "varint value extends past end of buffer",
}}
func (*packetV2Suite) TestParsePacketV2(c *gc.C) {
for i, test := range parsePacketV2Tests {
c.Logf("test %d: %v", i, test.about)
data, p, err := parsePacketV2([]byte(test.data))
if test.expectError != "" {
c.Assert(err, gc.ErrorMatches, test.expectError)
c.Assert(data, gc.IsNil)
c.Assert(p, gc.DeepEquals, packetV2{})
} else {
c.Assert(err, gc.IsNil)
c.Assert(p, jc.DeepEquals, test.expectPacket)
}
}
}
var parseSectionV2Tests = []struct {
about string
data string
expectData string
expectPackets []packetV2
expectError string
}{{
about: "no packets",
data: "\x00",
}, {
about: "one packet",
data: "\x02\x03xyz\x00",
expectPackets: []packetV2{{
fieldType: 2,
data: []byte("xyz"),
}},
}, {
about: "two packets",
data: "\x02\x03xyz\x07\x05abcde\x00",
expectPackets: []packetV2{{
fieldType: 2,
data: []byte("xyz"),
}, {
fieldType: 7,
data: []byte("abcde"),
}},
}, {
about: "unterminated section",
data: "\x02\x03xyz\x07\x05abcde",
expectError: "section extends past end of buffer",
}, {
about: "out of order fields",
data: "\x07\x05abcde\x02\x03xyz\x00",
expectError: "fields out of order",
}, {
about: "bad packet",
data: "\x07\x05abcde\xff",
expectError: "varint value extends past end of buffer",
}}
func (*packetV2Suite) TestParseSectionV2(c *gc.C) {
for i, test := range parseSectionV2Tests {
c.Logf("test %d: %v", i, test.about)
data, ps, err := parseSectionV2([]byte(test.data))
if test.expectError != "" {
c.Assert(err, gc.ErrorMatches, test.expectError)
c.Assert(data, gc.IsNil)
c.Assert(ps, gc.IsNil)
} else {
c.Assert(err, gc.IsNil)
c.Assert(ps, jc.DeepEquals, test.expectPackets)
}
}
}

View file

@ -0,0 +1,102 @@
package macaroon
import (
"fmt"
)
// Trace holds all toperations involved in verifying a macaroon,
// and the root key used as the initial verification key.
// This can be useful for debugging macaroon implementations.
type Trace struct {
RootKey []byte
Ops []TraceOp
}
// Results returns the output from all operations in the Trace.
// The result from ts.Ops[i] will be in the i'th element of the
// returned slice.
// When a trace has resulted in a failure, the
// last element will be nil.
func (t Trace) Results() [][]byte {
r := make([][]byte, len(t.Ops))
input := t.RootKey
for i, op := range t.Ops {
input = op.Result(input)
r[i] = input
}
return r
}
// TraceOp holds one possible operation when verifying a macaroon.
type TraceOp struct {
Kind TraceOpKind `json:"kind"`
Data1 []byte `json:"data1,omitempty"`
Data2 []byte `json:"data2,omitempty"`
}
// Result returns the result of computing the given
// operation with the given input data.
// If op is TraceFail, it returns nil.
func (op TraceOp) Result(input []byte) []byte {
switch op.Kind {
case TraceMakeKey:
return makeKey(input)[:]
case TraceHash:
if len(op.Data2) == 0 {
return keyedHash(bytesToKey(input), op.Data1)[:]
}
return keyedHash2(bytesToKey(input), op.Data1, op.Data2)[:]
case TraceBind:
return bindForRequest(op.Data1, bytesToKey(input))[:]
case TraceFail:
return nil
default:
panic(fmt.Errorf("unknown trace operation kind %d", op.Kind))
}
}
func bytesToKey(data []byte) *[keyLen]byte {
var key [keyLen]byte
if len(data) != keyLen {
panic(fmt.Errorf("unexpected input key length; got %d want %d", len(data), keyLen))
}
copy(key[:], data)
return &key
}
// TraceOpKind represents the kind of a macaroon verification operation.
type TraceOpKind int
const (
TraceInvalid = TraceOpKind(iota)
// TraceMakeKey represents the operation of calculating a
// fixed length root key from the variable length input key.
TraceMakeKey
// TraceHash represents a keyed hash operation with one
// or two values. If there is only one value, it will be in Data1.
TraceHash
// TraceBind represents the operation of binding a discharge macaroon
// to its primary macaroon. Data1 holds the signature of the primary
// macaroon.
TraceBind
// TraceFail represents a verification failure. If present, this will always
// be the last operation in a trace.
TraceFail
)
var traceOps = []string{
TraceInvalid: "invalid",
TraceMakeKey: "makekey",
TraceHash: "hash",
TraceBind: "bind",
TraceFail: "fail",
}
// String returns a string representation of the operation.
func (k TraceOpKind) String() string {
return traceOps[k]
}

View file

@ -0,0 +1,53 @@
// Copyright 2016 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 secretbox_test
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"golang.org/x/crypto/nacl/secretbox"
)
func Example() {
// Load your secret key from a safe place and reuse it across multiple
// Seal calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
secretKeyBytes, err := hex.DecodeString("6368616e676520746869732070617373776f726420746f206120736563726574")
if err != nil {
panic(err)
}
var secretKey [32]byte
copy(secretKey[:], secretKeyBytes)
// You must use a different nonce for each message you encrypt with the
// same key. Since the nonce here is 192 bits long, a random value
// provides a sufficiently small probability of repeats.
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
// This encrypts "hello world" and appends the result to the nonce.
encrypted := secretbox.Seal(nonce[:], []byte("hello world"), &nonce, &secretKey)
// When you decrypt, you must use the same nonce and key you used to
// encrypt the message. One way to achieve this is to store the nonce
// alongside the encrypted message. Above, we stored the nonce in the first
// 24 bytes of the encrypted text.
var decryptNonce [24]byte
copy(decryptNonce[:], encrypted[:24])
decrypted, ok := secretbox.Open(nil, encrypted[24:], &decryptNonce, &secretKey)
if !ok {
panic("decryption error")
}
fmt.Println(string(decrypted))
// Output: hello world
}

View file

@ -0,0 +1,166 @@
// Copyright 2012 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 secretbox encrypts and authenticates small messages.
Secretbox uses XSalsa20 and Poly1305 to encrypt and authenticate messages with
secret-key cryptography. The length of messages is not hidden.
It is the caller's responsibility to ensure the uniqueness of noncesfor
example, by using nonce 1 for the first message, nonce 2 for the second
message, etc. Nonces are long enough that randomly generated nonces have
negligible risk of collision.
Messages should be small because:
1. The whole message needs to be held in memory to be processed.
2. Using large messages pressures implementations on small machines to decrypt
and process plaintext before authenticating it. This is very dangerous, and
this API does not allow it, but a protocol that uses excessive message sizes
might present some implementations with no other choice.
3. Fixed overheads will be sufficiently amortised by messages as small as 8KB.
4. Performance may be improved by working with messages that fit into data caches.
Thus large amounts of data should be chunked so that each message is small.
(Each message still needs a unique nonce.) If in doubt, 16KB is a reasonable
chunk size.
This package is interoperable with NaCl: https://nacl.cr.yp.to/secretbox.html.
*/
package secretbox // import "golang.org/x/crypto/nacl/secretbox"
import (
"golang.org/x/crypto/poly1305"
"golang.org/x/crypto/salsa20/salsa"
)
// Overhead is the number of bytes of overhead when boxing a message.
const Overhead = poly1305.TagSize
// setup produces a sub-key and Salsa20 counter given a nonce and key.
func setup(subKey *[32]byte, counter *[16]byte, nonce *[24]byte, key *[32]byte) {
// We use XSalsa20 for encryption so first we need to generate a
// key and nonce with HSalsa20.
var hNonce [16]byte
copy(hNonce[:], nonce[:])
salsa.HSalsa20(subKey, &hNonce, key, &salsa.Sigma)
// The final 8 bytes of the original nonce form the new nonce.
copy(counter[:], nonce[16:])
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}
// Seal appends an encrypted and authenticated copy of message to out, which
// must not overlap message. The key and nonce pair must be unique for each
// distinct message and the output will be Overhead bytes longer than message.
func Seal(out, message []byte, nonce *[24]byte, key *[32]byte) []byte {
var subKey [32]byte
var counter [16]byte
setup(&subKey, &counter, nonce, key)
// The Poly1305 key is generated by encrypting 32 bytes of zeros. Since
// Salsa20 works with 64-byte blocks, we also generate 32 bytes of
// keystream as a side effect.
var firstBlock [64]byte
salsa.XORKeyStream(firstBlock[:], firstBlock[:], &counter, &subKey)
var poly1305Key [32]byte
copy(poly1305Key[:], firstBlock[:])
ret, out := sliceForAppend(out, len(message)+poly1305.TagSize)
// We XOR up to 32 bytes of message with the keystream generated from
// the first block.
firstMessageBlock := message
if len(firstMessageBlock) > 32 {
firstMessageBlock = firstMessageBlock[:32]
}
tagOut := out
out = out[poly1305.TagSize:]
for i, x := range firstMessageBlock {
out[i] = firstBlock[32+i] ^ x
}
message = message[len(firstMessageBlock):]
ciphertext := out
out = out[len(firstMessageBlock):]
// Now encrypt the rest.
counter[8] = 1
salsa.XORKeyStream(out, message, &counter, &subKey)
var tag [poly1305.TagSize]byte
poly1305.Sum(&tag, ciphertext, &poly1305Key)
copy(tagOut, tag[:])
return ret
}
// Open authenticates and decrypts a box produced by Seal and appends the
// message to out, which must not overlap box. The output will be Overhead
// bytes smaller than box.
func Open(out []byte, box []byte, nonce *[24]byte, key *[32]byte) ([]byte, bool) {
if len(box) < Overhead {
return nil, false
}
var subKey [32]byte
var counter [16]byte
setup(&subKey, &counter, nonce, key)
// The Poly1305 key is generated by encrypting 32 bytes of zeros. Since
// Salsa20 works with 64-byte blocks, we also generate 32 bytes of
// keystream as a side effect.
var firstBlock [64]byte
salsa.XORKeyStream(firstBlock[:], firstBlock[:], &counter, &subKey)
var poly1305Key [32]byte
copy(poly1305Key[:], firstBlock[:])
var tag [poly1305.TagSize]byte
copy(tag[:], box)
if !poly1305.Verify(&tag, box[poly1305.TagSize:], &poly1305Key) {
return nil, false
}
ret, out := sliceForAppend(out, len(box)-Overhead)
// We XOR up to 32 bytes of box with the keystream generated from
// the first block.
box = box[Overhead:]
firstMessageBlock := box
if len(firstMessageBlock) > 32 {
firstMessageBlock = firstMessageBlock[:32]
}
for i, x := range firstMessageBlock {
out[i] = firstBlock[32+i] ^ x
}
box = box[len(firstMessageBlock):]
out = out[len(firstMessageBlock):]
// Now decrypt the rest.
counter[8] = 1
salsa.XORKeyStream(out, box, &counter, &subKey)
return ret, true
}

View file

@ -0,0 +1,154 @@
// Copyright 2012 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 secretbox
import (
"bytes"
"crypto/rand"
"encoding/hex"
"testing"
)
func TestSealOpen(t *testing.T) {
var key [32]byte
var nonce [24]byte
rand.Reader.Read(key[:])
rand.Reader.Read(nonce[:])
var box, opened []byte
for msgLen := 0; msgLen < 128; msgLen += 17 {
message := make([]byte, msgLen)
rand.Reader.Read(message)
box = Seal(box[:0], message, &nonce, &key)
var ok bool
opened, ok = Open(opened[:0], box, &nonce, &key)
if !ok {
t.Errorf("%d: failed to open box", msgLen)
continue
}
if !bytes.Equal(opened, message) {
t.Errorf("%d: got %x, expected %x", msgLen, opened, message)
continue
}
}
for i := range box {
box[i] ^= 0x20
_, ok := Open(opened[:0], box, &nonce, &key)
if ok {
t.Errorf("box was opened after corrupting byte %d", i)
}
box[i] ^= 0x20
}
}
func TestSecretBox(t *testing.T) {
var key [32]byte
var nonce [24]byte
var message [64]byte
for i := range key[:] {
key[i] = 1
}
for i := range nonce[:] {
nonce[i] = 2
}
for i := range message[:] {
message[i] = 3
}
box := Seal(nil, message[:], &nonce, &key)
// expected was generated using the C implementation of NaCl.
expected, _ := hex.DecodeString("8442bc313f4626f1359e3b50122b6ce6fe66ddfe7d39d14e637eb4fd5b45beadab55198df6ab5368439792a23c87db70acb6156dc5ef957ac04f6276cf6093b84be77ff0849cc33e34b7254d5a8f65ad")
if !bytes.Equal(box, expected) {
t.Fatalf("box didn't match, got\n%x\n, expected\n%x", box, expected)
}
}
func TestAppend(t *testing.T) {
var key [32]byte
var nonce [24]byte
var message [8]byte
out := make([]byte, 4)
box := Seal(out, message[:], &nonce, &key)
if !bytes.Equal(box[:4], out[:4]) {
t.Fatalf("Seal didn't correctly append")
}
out = make([]byte, 4, 100)
box = Seal(out, message[:], &nonce, &key)
if !bytes.Equal(box[:4], out[:4]) {
t.Fatalf("Seal didn't correctly append with sufficient capacity.")
}
}
func benchmarkSealSize(b *testing.B, size int) {
message := make([]byte, size)
out := make([]byte, size+Overhead)
var nonce [24]byte
var key [32]byte
b.SetBytes(int64(size))
b.ResetTimer()
for i := 0; i < b.N; i++ {
out = Seal(out[:0], message, &nonce, &key)
}
}
func BenchmarkSeal8Bytes(b *testing.B) {
benchmarkSealSize(b, 8)
}
func BenchmarkSeal100Bytes(b *testing.B) {
benchmarkSealSize(b, 100)
}
func BenchmarkSeal1K(b *testing.B) {
benchmarkSealSize(b, 1024)
}
func BenchmarkSeal8K(b *testing.B) {
benchmarkSealSize(b, 8192)
}
func benchmarkOpenSize(b *testing.B, size int) {
msg := make([]byte, size)
result := make([]byte, size)
var nonce [24]byte
var key [32]byte
box := Seal(nil, msg, &nonce, &key)
b.SetBytes(int64(size))
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, ok := Open(result[:0], box, &nonce, &key); !ok {
panic("Open failed")
}
}
}
func BenchmarkOpen8Bytes(b *testing.B) {
benchmarkOpenSize(b, 8)
}
func BenchmarkOpen100Bytes(b *testing.B) {
benchmarkOpenSize(b, 100)
}
func BenchmarkOpen1K(b *testing.B) {
benchmarkOpenSize(b, 1024)
}
func BenchmarkOpen8K(b *testing.B) {
benchmarkOpenSize(b, 8192)
}

View file

@ -0,0 +1,33 @@
// Copyright 2012 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 poly1305 implements Poly1305 one-time message authentication code as
specified in https://cr.yp.to/mac/poly1305-20050329.pdf.
Poly1305 is a fast, one-time authentication function. It is infeasible for an
attacker to generate an authenticator for a message without the key. However, a
key must only be used for a single message. Authenticating two different
messages with the same key allows an attacker to forge authenticators for other
messages with the same key.
Poly1305 was originally coupled with AES in order to make Poly1305-AES. AES was
used with a fixed key in order to generate one-time keys from an nonce.
However, in this package AES isn't used and the one-time key is specified
directly.
*/
package poly1305 // import "golang.org/x/crypto/poly1305"
import "crypto/subtle"
// TagSize is the size, in bytes, of a poly1305 authenticator.
const TagSize = 16
// Verify returns true if mac is a valid authenticator for m with the given
// key.
func Verify(mac *[16]byte, m []byte, key *[32]byte) bool {
var tmp [16]byte
Sum(&tmp, m, key)
return subtle.ConstantTimeCompare(tmp[:], mac[:]) == 1
}

View file

@ -0,0 +1,159 @@
// Copyright 2012 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 poly1305
import (
"bytes"
"encoding/hex"
"flag"
"testing"
"unsafe"
)
var stressFlag = flag.Bool("stress", false, "run slow stress tests")
var testData = []struct {
in, k, correct []byte
}{
{
[]byte("Hello world!"),
[]byte("this is 32-byte key for Poly1305"),
[]byte{0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0},
},
{
make([]byte, 32),
[]byte("this is 32-byte key for Poly1305"),
[]byte{0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07},
},
{
make([]byte, 2007),
[]byte("this is 32-byte key for Poly1305"),
[]byte{0xda, 0x84, 0xbc, 0xab, 0x02, 0x67, 0x6c, 0x38, 0xcd, 0xb0, 0x15, 0x60, 0x42, 0x74, 0xc2, 0xaa},
},
{
make([]byte, 2007),
make([]byte, 32),
make([]byte, 16),
},
{
// This test triggers an edge-case. See https://go-review.googlesource.com/#/c/30101/.
[]byte{0x81, 0xd8, 0xb2, 0xe4, 0x6a, 0x25, 0x21, 0x3b, 0x58, 0xfe, 0xe4, 0x21, 0x3a, 0x2a, 0x28, 0xe9, 0x21, 0xc1, 0x2a, 0x96, 0x32, 0x51, 0x6d, 0x3b, 0x73, 0x27, 0x27, 0x27, 0xbe, 0xcf, 0x21, 0x29},
[]byte{0x3b, 0x3a, 0x29, 0xe9, 0x3b, 0x21, 0x3a, 0x5c, 0x5c, 0x3b, 0x3b, 0x05, 0x3a, 0x3a, 0x8c, 0x0d},
[]byte{0x6d, 0xc1, 0x8b, 0x8c, 0x34, 0x4c, 0xd7, 0x99, 0x27, 0x11, 0x8b, 0xbe, 0x84, 0xb7, 0xf3, 0x14},
},
{
// This test generates a result of (2^130-1) % (2^130-5).
[]byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
[]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
[]byte{4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
{
// This test generates a result of (2^130-6) % (2^130-5).
[]byte{
0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
[]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
[]byte{0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
},
{
// This test generates a result of (2^130-5) % (2^130-5).
[]byte{
0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
[]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
}
func testSum(t *testing.T, unaligned bool) {
var out [16]byte
var key [32]byte
for i, v := range testData {
in := v.in
if unaligned {
in = unalignBytes(in)
}
copy(key[:], v.k)
Sum(&out, in, &key)
if !bytes.Equal(out[:], v.correct) {
t.Errorf("%d: expected %x, got %x", i, v.correct, out[:])
}
}
}
func TestBurnin(t *testing.T) {
// This test can be used to sanity-check significant changes. It can
// take about many minutes to run, even on fast machines. It's disabled
// by default.
if !*stressFlag {
t.Skip("skipping without -stress")
}
var key [32]byte
var input [25]byte
var output [16]byte
for i := range key {
key[i] = 1
}
for i := range input {
input[i] = 2
}
for i := uint64(0); i < 1e10; i++ {
Sum(&output, input[:], &key)
copy(key[0:], output[:])
copy(key[16:], output[:])
copy(input[:], output[:])
copy(input[16:], output[:])
}
const expected = "5e3b866aea0b636d240c83c428f84bfa"
if got := hex.EncodeToString(output[:]); got != expected {
t.Errorf("expected %s, got %s", expected, got)
}
}
func TestSum(t *testing.T) { testSum(t, false) }
func TestSumUnaligned(t *testing.T) { testSum(t, true) }
func benchmark(b *testing.B, size int, unaligned bool) {
var out [16]byte
var key [32]byte
in := make([]byte, size)
if unaligned {
in = unalignBytes(in)
}
b.SetBytes(int64(len(in)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sum(&out, in, &key)
}
}
func Benchmark64(b *testing.B) { benchmark(b, 64, false) }
func Benchmark1K(b *testing.B) { benchmark(b, 1024, false) }
func Benchmark64Unaligned(b *testing.B) { benchmark(b, 64, true) }
func Benchmark1KUnaligned(b *testing.B) { benchmark(b, 1024, true) }
func unalignBytes(in []byte) []byte {
out := make([]byte, len(in)+1)
if uintptr(unsafe.Pointer(&out[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
out = out[1:]
} else {
out = out[:len(in)]
}
copy(out, in)
return out
}

View file

@ -0,0 +1,22 @@
// Copyright 2012 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.
// +build amd64,!gccgo,!appengine
package poly1305
// This function is implemented in sum_amd64.s
//go:noescape
func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]byte)
// Sum generates an authenticator for m using a one-time key and puts the
// 16-byte result into out. Authenticating two different messages with the same
// key allows an attacker to forge messages at will.
func Sum(out *[16]byte, m []byte, key *[32]byte) {
var mPtr *byte
if len(m) > 0 {
mPtr = &m[0]
}
poly1305(out, mPtr, uint64(len(m)), key)
}

View file

@ -0,0 +1,125 @@
// Copyright 2012 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.
// +build amd64,!gccgo,!appengine
#include "textflag.h"
#define POLY1305_ADD(msg, h0, h1, h2) \
ADDQ 0(msg), h0; \
ADCQ 8(msg), h1; \
ADCQ $1, h2; \
LEAQ 16(msg), msg
#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3) \
MOVQ r0, AX; \
MULQ h0; \
MOVQ AX, t0; \
MOVQ DX, t1; \
MOVQ r0, AX; \
MULQ h1; \
ADDQ AX, t1; \
ADCQ $0, DX; \
MOVQ r0, t2; \
IMULQ h2, t2; \
ADDQ DX, t2; \
\
MOVQ r1, AX; \
MULQ h0; \
ADDQ AX, t1; \
ADCQ $0, DX; \
MOVQ DX, h0; \
MOVQ r1, t3; \
IMULQ h2, t3; \
MOVQ r1, AX; \
MULQ h1; \
ADDQ AX, t2; \
ADCQ DX, t3; \
ADDQ h0, t2; \
ADCQ $0, t3; \
\
MOVQ t0, h0; \
MOVQ t1, h1; \
MOVQ t2, h2; \
ANDQ $3, h2; \
MOVQ t2, t0; \
ANDQ $0xFFFFFFFFFFFFFFFC, t0; \
ADDQ t0, h0; \
ADCQ t3, h1; \
ADCQ $0, h2; \
SHRQ $2, t3, t2; \
SHRQ $2, t3; \
ADDQ t2, h0; \
ADCQ t3, h1; \
ADCQ $0, h2
DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF
DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC
GLOBL ·poly1305Mask<>(SB), RODATA, $16
// func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]key)
TEXT ·poly1305(SB), $0-32
MOVQ out+0(FP), DI
MOVQ m+8(FP), SI
MOVQ mlen+16(FP), R15
MOVQ key+24(FP), AX
MOVQ 0(AX), R11
MOVQ 8(AX), R12
ANDQ ·poly1305Mask<>(SB), R11 // r0
ANDQ ·poly1305Mask<>+8(SB), R12 // r1
XORQ R8, R8 // h0
XORQ R9, R9 // h1
XORQ R10, R10 // h2
CMPQ R15, $16
JB bytes_between_0_and_15
loop:
POLY1305_ADD(SI, R8, R9, R10)
multiply:
POLY1305_MUL(R8, R9, R10, R11, R12, BX, CX, R13, R14)
SUBQ $16, R15
CMPQ R15, $16
JAE loop
bytes_between_0_and_15:
TESTQ R15, R15
JZ done
MOVQ $1, BX
XORQ CX, CX
XORQ R13, R13
ADDQ R15, SI
flush_buffer:
SHLQ $8, BX, CX
SHLQ $8, BX
MOVB -1(SI), R13
XORQ R13, BX
DECQ SI
DECQ R15
JNZ flush_buffer
ADDQ BX, R8
ADCQ CX, R9
ADCQ $0, R10
MOVQ $16, R15
JMP multiply
done:
MOVQ R8, AX
MOVQ R9, BX
SUBQ $0xFFFFFFFFFFFFFFFB, AX
SBBQ $0xFFFFFFFFFFFFFFFF, BX
SBBQ $3, R10
CMOVQCS R8, AX
CMOVQCS R9, BX
MOVQ key+24(FP), R8
ADDQ 16(R8), AX
ADCQ 24(R8), BX
MOVQ AX, 0(DI)
MOVQ BX, 8(DI)
RET

View file

@ -0,0 +1,22 @@
// Copyright 2015 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.
// +build arm,!gccgo,!appengine,!nacl
package poly1305
// This function is implemented in sum_arm.s
//go:noescape
func poly1305_auth_armv6(out *[16]byte, m *byte, mlen uint32, key *[32]byte)
// Sum generates an authenticator for m using a one-time key and puts the
// 16-byte result into out. Authenticating two different messages with the same
// key allows an attacker to forge messages at will.
func Sum(out *[16]byte, m []byte, key *[32]byte) {
var mPtr *byte
if len(m) > 0 {
mPtr = &m[0]
}
poly1305_auth_armv6(out, mPtr, uint32(len(m)), key)
}

View file

@ -0,0 +1,427 @@
// Copyright 2015 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.
// +build arm,!gccgo,!appengine,!nacl
#include "textflag.h"
// This code was translated into a form compatible with 5a from the public
// domain source by Andrew Moon: github.com/floodyberry/poly1305-opt/blob/master/app/extensions/poly1305.
DATA ·poly1305_init_constants_armv6<>+0x00(SB)/4, $0x3ffffff
DATA ·poly1305_init_constants_armv6<>+0x04(SB)/4, $0x3ffff03
DATA ·poly1305_init_constants_armv6<>+0x08(SB)/4, $0x3ffc0ff
DATA ·poly1305_init_constants_armv6<>+0x0c(SB)/4, $0x3f03fff
DATA ·poly1305_init_constants_armv6<>+0x10(SB)/4, $0x00fffff
GLOBL ·poly1305_init_constants_armv6<>(SB), 8, $20
// Warning: the linker may use R11 to synthesize certain instructions. Please
// take care and verify that no synthetic instructions use it.
TEXT poly1305_init_ext_armv6<>(SB), NOSPLIT, $0
// Needs 16 bytes of stack and 64 bytes of space pointed to by R0. (It
// might look like it's only 60 bytes of space but the final four bytes
// will be written by another function.) We need to skip over four
// bytes of stack because that's saving the value of 'g'.
ADD $4, R13, R8
MOVM.IB [R4-R7], (R8)
MOVM.IA.W (R1), [R2-R5]
MOVW $·poly1305_init_constants_armv6<>(SB), R7
MOVW R2, R8
MOVW R2>>26, R9
MOVW R3>>20, g
MOVW R4>>14, R11
MOVW R5>>8, R12
ORR R3<<6, R9, R9
ORR R4<<12, g, g
ORR R5<<18, R11, R11
MOVM.IA (R7), [R2-R6]
AND R8, R2, R2
AND R9, R3, R3
AND g, R4, R4
AND R11, R5, R5
AND R12, R6, R6
MOVM.IA.W [R2-R6], (R0)
EOR R2, R2, R2
EOR R3, R3, R3
EOR R4, R4, R4
EOR R5, R5, R5
EOR R6, R6, R6
MOVM.IA.W [R2-R6], (R0)
MOVM.IA.W (R1), [R2-R5]
MOVM.IA [R2-R6], (R0)
ADD $20, R13, R0
MOVM.DA (R0), [R4-R7]
RET
#define MOVW_UNALIGNED(Rsrc, Rdst, Rtmp, offset) \
MOVBU (offset+0)(Rsrc), Rtmp; \
MOVBU Rtmp, (offset+0)(Rdst); \
MOVBU (offset+1)(Rsrc), Rtmp; \
MOVBU Rtmp, (offset+1)(Rdst); \
MOVBU (offset+2)(Rsrc), Rtmp; \
MOVBU Rtmp, (offset+2)(Rdst); \
MOVBU (offset+3)(Rsrc), Rtmp; \
MOVBU Rtmp, (offset+3)(Rdst)
TEXT poly1305_blocks_armv6<>(SB), NOSPLIT, $0
// Needs 24 bytes of stack for saved registers and then 88 bytes of
// scratch space after that. We assume that 24 bytes at (R13) have
// already been used: four bytes for the link register saved in the
// prelude of poly1305_auth_armv6, four bytes for saving the value of g
// in that function and 16 bytes of scratch space used around
// poly1305_finish_ext_armv6_skip1.
ADD $24, R13, R12
MOVM.IB [R4-R8, R14], (R12)
MOVW R0, 88(R13)
MOVW R1, 92(R13)
MOVW R2, 96(R13)
MOVW R1, R14
MOVW R2, R12
MOVW 56(R0), R8
WORD $0xe1180008 // TST R8, R8 not working see issue 5921
EOR R6, R6, R6
MOVW.EQ $(1<<24), R6
MOVW R6, 84(R13)
ADD $116, R13, g
MOVM.IA (R0), [R0-R9]
MOVM.IA [R0-R4], (g)
CMP $16, R12
BLO poly1305_blocks_armv6_done
poly1305_blocks_armv6_mainloop:
WORD $0xe31e0003 // TST R14, #3 not working see issue 5921
BEQ poly1305_blocks_armv6_mainloop_aligned
ADD $100, R13, g
MOVW_UNALIGNED(R14, g, R0, 0)
MOVW_UNALIGNED(R14, g, R0, 4)
MOVW_UNALIGNED(R14, g, R0, 8)
MOVW_UNALIGNED(R14, g, R0, 12)
MOVM.IA (g), [R0-R3]
ADD $16, R14
B poly1305_blocks_armv6_mainloop_loaded
poly1305_blocks_armv6_mainloop_aligned:
MOVM.IA.W (R14), [R0-R3]
poly1305_blocks_armv6_mainloop_loaded:
MOVW R0>>26, g
MOVW R1>>20, R11
MOVW R2>>14, R12
MOVW R14, 92(R13)
MOVW R3>>8, R4
ORR R1<<6, g, g
ORR R2<<12, R11, R11
ORR R3<<18, R12, R12
BIC $0xfc000000, R0, R0
BIC $0xfc000000, g, g
MOVW 84(R13), R3
BIC $0xfc000000, R11, R11
BIC $0xfc000000, R12, R12
ADD R0, R5, R5
ADD g, R6, R6
ORR R3, R4, R4
ADD R11, R7, R7
ADD $116, R13, R14
ADD R12, R8, R8
ADD R4, R9, R9
MOVM.IA (R14), [R0-R4]
MULLU R4, R5, (R11, g)
MULLU R3, R5, (R14, R12)
MULALU R3, R6, (R11, g)
MULALU R2, R6, (R14, R12)
MULALU R2, R7, (R11, g)
MULALU R1, R7, (R14, R12)
ADD R4<<2, R4, R4
ADD R3<<2, R3, R3
MULALU R1, R8, (R11, g)
MULALU R0, R8, (R14, R12)
MULALU R0, R9, (R11, g)
MULALU R4, R9, (R14, R12)
MOVW g, 76(R13)
MOVW R11, 80(R13)
MOVW R12, 68(R13)
MOVW R14, 72(R13)
MULLU R2, R5, (R11, g)
MULLU R1, R5, (R14, R12)
MULALU R1, R6, (R11, g)
MULALU R0, R6, (R14, R12)
MULALU R0, R7, (R11, g)
MULALU R4, R7, (R14, R12)
ADD R2<<2, R2, R2
ADD R1<<2, R1, R1
MULALU R4, R8, (R11, g)
MULALU R3, R8, (R14, R12)
MULALU R3, R9, (R11, g)
MULALU R2, R9, (R14, R12)
MOVW g, 60(R13)
MOVW R11, 64(R13)
MOVW R12, 52(R13)
MOVW R14, 56(R13)
MULLU R0, R5, (R11, g)
MULALU R4, R6, (R11, g)
MULALU R3, R7, (R11, g)
MULALU R2, R8, (R11, g)
MULALU R1, R9, (R11, g)
ADD $52, R13, R0
MOVM.IA (R0), [R0-R7]
MOVW g>>26, R12
MOVW R4>>26, R14
ORR R11<<6, R12, R12
ORR R5<<6, R14, R14
BIC $0xfc000000, g, g
BIC $0xfc000000, R4, R4
ADD.S R12, R0, R0
ADC $0, R1, R1
ADD.S R14, R6, R6
ADC $0, R7, R7
MOVW R0>>26, R12
MOVW R6>>26, R14
ORR R1<<6, R12, R12
ORR R7<<6, R14, R14
BIC $0xfc000000, R0, R0
BIC $0xfc000000, R6, R6
ADD R14<<2, R14, R14
ADD.S R12, R2, R2
ADC $0, R3, R3
ADD R14, g, g
MOVW R2>>26, R12
MOVW g>>26, R14
ORR R3<<6, R12, R12
BIC $0xfc000000, g, R5
BIC $0xfc000000, R2, R7
ADD R12, R4, R4
ADD R14, R0, R0
MOVW R4>>26, R12
BIC $0xfc000000, R4, R8
ADD R12, R6, R9
MOVW 96(R13), R12
MOVW 92(R13), R14
MOVW R0, R6
CMP $32, R12
SUB $16, R12, R12
MOVW R12, 96(R13)
BHS poly1305_blocks_armv6_mainloop
poly1305_blocks_armv6_done:
MOVW 88(R13), R12
MOVW R5, 20(R12)
MOVW R6, 24(R12)
MOVW R7, 28(R12)
MOVW R8, 32(R12)
MOVW R9, 36(R12)
ADD $48, R13, R0
MOVM.DA (R0), [R4-R8, R14]
RET
#define MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp) \
MOVBU.P 1(Rsrc), Rtmp; \
MOVBU.P Rtmp, 1(Rdst); \
MOVBU.P 1(Rsrc), Rtmp; \
MOVBU.P Rtmp, 1(Rdst)
#define MOVWP_UNALIGNED(Rsrc, Rdst, Rtmp) \
MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp); \
MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp)
// func poly1305_auth_armv6(out *[16]byte, m *byte, mlen uint32, key *[32]key)
TEXT ·poly1305_auth_armv6(SB), $196-16
// The value 196, just above, is the sum of 64 (the size of the context
// structure) and 132 (the amount of stack needed).
//
// At this point, the stack pointer (R13) has been moved down. It
// points to the saved link register and there's 196 bytes of free
// space above it.
//
// The stack for this function looks like:
//
// +---------------------
// |
// | 64 bytes of context structure
// |
// +---------------------
// |
// | 112 bytes for poly1305_blocks_armv6
// |
// +---------------------
// | 16 bytes of final block, constructed at
// | poly1305_finish_ext_armv6_skip8
// +---------------------
// | four bytes of saved 'g'
// +---------------------
// | lr, saved by prelude <- R13 points here
// +---------------------
MOVW g, 4(R13)
MOVW out+0(FP), R4
MOVW m+4(FP), R5
MOVW mlen+8(FP), R6
MOVW key+12(FP), R7
ADD $136, R13, R0 // 136 = 4 + 4 + 16 + 112
MOVW R7, R1
// poly1305_init_ext_armv6 will write to the stack from R13+4, but
// that's ok because none of the other values have been written yet.
BL poly1305_init_ext_armv6<>(SB)
BIC.S $15, R6, R2
BEQ poly1305_auth_armv6_noblocks
ADD $136, R13, R0
MOVW R5, R1
ADD R2, R5, R5
SUB R2, R6, R6
BL poly1305_blocks_armv6<>(SB)
poly1305_auth_armv6_noblocks:
ADD $136, R13, R0
MOVW R5, R1
MOVW R6, R2
MOVW R4, R3
MOVW R0, R5
MOVW R1, R6
MOVW R2, R7
MOVW R3, R8
AND.S R2, R2, R2
BEQ poly1305_finish_ext_armv6_noremaining
EOR R0, R0
ADD $8, R13, R9 // 8 = offset to 16 byte scratch space
MOVW R0, (R9)
MOVW R0, 4(R9)
MOVW R0, 8(R9)
MOVW R0, 12(R9)
WORD $0xe3110003 // TST R1, #3 not working see issue 5921
BEQ poly1305_finish_ext_armv6_aligned
WORD $0xe3120008 // TST R2, #8 not working see issue 5921
BEQ poly1305_finish_ext_armv6_skip8
MOVWP_UNALIGNED(R1, R9, g)
MOVWP_UNALIGNED(R1, R9, g)
poly1305_finish_ext_armv6_skip8:
WORD $0xe3120004 // TST $4, R2 not working see issue 5921
BEQ poly1305_finish_ext_armv6_skip4
MOVWP_UNALIGNED(R1, R9, g)
poly1305_finish_ext_armv6_skip4:
WORD $0xe3120002 // TST $2, R2 not working see issue 5921
BEQ poly1305_finish_ext_armv6_skip2
MOVHUP_UNALIGNED(R1, R9, g)
B poly1305_finish_ext_armv6_skip2
poly1305_finish_ext_armv6_aligned:
WORD $0xe3120008 // TST R2, #8 not working see issue 5921
BEQ poly1305_finish_ext_armv6_skip8_aligned
MOVM.IA.W (R1), [g-R11]
MOVM.IA.W [g-R11], (R9)
poly1305_finish_ext_armv6_skip8_aligned:
WORD $0xe3120004 // TST $4, R2 not working see issue 5921
BEQ poly1305_finish_ext_armv6_skip4_aligned
MOVW.P 4(R1), g
MOVW.P g, 4(R9)
poly1305_finish_ext_armv6_skip4_aligned:
WORD $0xe3120002 // TST $2, R2 not working see issue 5921
BEQ poly1305_finish_ext_armv6_skip2
MOVHU.P 2(R1), g
MOVH.P g, 2(R9)
poly1305_finish_ext_armv6_skip2:
WORD $0xe3120001 // TST $1, R2 not working see issue 5921
BEQ poly1305_finish_ext_armv6_skip1
MOVBU.P 1(R1), g
MOVBU.P g, 1(R9)
poly1305_finish_ext_armv6_skip1:
MOVW $1, R11
MOVBU R11, 0(R9)
MOVW R11, 56(R5)
MOVW R5, R0
ADD $8, R13, R1
MOVW $16, R2
BL poly1305_blocks_armv6<>(SB)
poly1305_finish_ext_armv6_noremaining:
MOVW 20(R5), R0
MOVW 24(R5), R1
MOVW 28(R5), R2
MOVW 32(R5), R3
MOVW 36(R5), R4
MOVW R4>>26, R12
BIC $0xfc000000, R4, R4
ADD R12<<2, R12, R12
ADD R12, R0, R0
MOVW R0>>26, R12
BIC $0xfc000000, R0, R0
ADD R12, R1, R1
MOVW R1>>26, R12
BIC $0xfc000000, R1, R1
ADD R12, R2, R2
MOVW R2>>26, R12
BIC $0xfc000000, R2, R2
ADD R12, R3, R3
MOVW R3>>26, R12
BIC $0xfc000000, R3, R3
ADD R12, R4, R4
ADD $5, R0, R6
MOVW R6>>26, R12
BIC $0xfc000000, R6, R6
ADD R12, R1, R7
MOVW R7>>26, R12
BIC $0xfc000000, R7, R7
ADD R12, R2, g
MOVW g>>26, R12
BIC $0xfc000000, g, g
ADD R12, R3, R11
MOVW $-(1<<26), R12
ADD R11>>26, R12, R12
BIC $0xfc000000, R11, R11
ADD R12, R4, R9
MOVW R9>>31, R12
SUB $1, R12
AND R12, R6, R6
AND R12, R7, R7
AND R12, g, g
AND R12, R11, R11
AND R12, R9, R9
MVN R12, R12
AND R12, R0, R0
AND R12, R1, R1
AND R12, R2, R2
AND R12, R3, R3
AND R12, R4, R4
ORR R6, R0, R0
ORR R7, R1, R1
ORR g, R2, R2
ORR R11, R3, R3
ORR R9, R4, R4
ORR R1<<26, R0, R0
MOVW R1>>6, R1
ORR R2<<20, R1, R1
MOVW R2>>12, R2
ORR R3<<14, R2, R2
MOVW R3>>18, R3
ORR R4<<8, R3, R3
MOVW 40(R5), R6
MOVW 44(R5), R7
MOVW 48(R5), g
MOVW 52(R5), R11
ADD.S R6, R0, R0
ADC.S R7, R1, R1
ADC.S g, R2, R2
ADC.S R11, R3, R3
MOVM.IA [R0-R3], (R8)
MOVW R5, R12
EOR R0, R0, R0
EOR R1, R1, R1
EOR R2, R2, R2
EOR R3, R3, R3
EOR R4, R4, R4
EOR R5, R5, R5
EOR R6, R6, R6
EOR R7, R7, R7
MOVM.IA.W [R0-R7], (R12)
MOVM.IA [R0-R7], (R12)
MOVW 4(R13), g
RET

View file

@ -0,0 +1,141 @@
// Copyright 2012 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.
// +build !amd64,!arm gccgo appengine nacl
package poly1305
import "encoding/binary"
// Sum generates an authenticator for msg using a one-time key and puts the
// 16-byte result into out. Authenticating two different messages with the same
// key allows an attacker to forge messages at will.
func Sum(out *[TagSize]byte, msg []byte, key *[32]byte) {
var (
h0, h1, h2, h3, h4 uint32 // the hash accumulators
r0, r1, r2, r3, r4 uint64 // the r part of the key
)
r0 = uint64(binary.LittleEndian.Uint32(key[0:]) & 0x3ffffff)
r1 = uint64((binary.LittleEndian.Uint32(key[3:]) >> 2) & 0x3ffff03)
r2 = uint64((binary.LittleEndian.Uint32(key[6:]) >> 4) & 0x3ffc0ff)
r3 = uint64((binary.LittleEndian.Uint32(key[9:]) >> 6) & 0x3f03fff)
r4 = uint64((binary.LittleEndian.Uint32(key[12:]) >> 8) & 0x00fffff)
R1, R2, R3, R4 := r1*5, r2*5, r3*5, r4*5
for len(msg) >= TagSize {
// h += msg
h0 += binary.LittleEndian.Uint32(msg[0:]) & 0x3ffffff
h1 += (binary.LittleEndian.Uint32(msg[3:]) >> 2) & 0x3ffffff
h2 += (binary.LittleEndian.Uint32(msg[6:]) >> 4) & 0x3ffffff
h3 += (binary.LittleEndian.Uint32(msg[9:]) >> 6) & 0x3ffffff
h4 += (binary.LittleEndian.Uint32(msg[12:]) >> 8) | (1 << 24)
// h *= r
d0 := (uint64(h0) * r0) + (uint64(h1) * R4) + (uint64(h2) * R3) + (uint64(h3) * R2) + (uint64(h4) * R1)
d1 := (d0 >> 26) + (uint64(h0) * r1) + (uint64(h1) * r0) + (uint64(h2) * R4) + (uint64(h3) * R3) + (uint64(h4) * R2)
d2 := (d1 >> 26) + (uint64(h0) * r2) + (uint64(h1) * r1) + (uint64(h2) * r0) + (uint64(h3) * R4) + (uint64(h4) * R3)
d3 := (d2 >> 26) + (uint64(h0) * r3) + (uint64(h1) * r2) + (uint64(h2) * r1) + (uint64(h3) * r0) + (uint64(h4) * R4)
d4 := (d3 >> 26) + (uint64(h0) * r4) + (uint64(h1) * r3) + (uint64(h2) * r2) + (uint64(h3) * r1) + (uint64(h4) * r0)
// h %= p
h0 = uint32(d0) & 0x3ffffff
h1 = uint32(d1) & 0x3ffffff
h2 = uint32(d2) & 0x3ffffff
h3 = uint32(d3) & 0x3ffffff
h4 = uint32(d4) & 0x3ffffff
h0 += uint32(d4>>26) * 5
h1 += h0 >> 26
h0 = h0 & 0x3ffffff
msg = msg[TagSize:]
}
if len(msg) > 0 {
var block [TagSize]byte
off := copy(block[:], msg)
block[off] = 0x01
// h += msg
h0 += binary.LittleEndian.Uint32(block[0:]) & 0x3ffffff
h1 += (binary.LittleEndian.Uint32(block[3:]) >> 2) & 0x3ffffff
h2 += (binary.LittleEndian.Uint32(block[6:]) >> 4) & 0x3ffffff
h3 += (binary.LittleEndian.Uint32(block[9:]) >> 6) & 0x3ffffff
h4 += (binary.LittleEndian.Uint32(block[12:]) >> 8)
// h *= r
d0 := (uint64(h0) * r0) + (uint64(h1) * R4) + (uint64(h2) * R3) + (uint64(h3) * R2) + (uint64(h4) * R1)
d1 := (d0 >> 26) + (uint64(h0) * r1) + (uint64(h1) * r0) + (uint64(h2) * R4) + (uint64(h3) * R3) + (uint64(h4) * R2)
d2 := (d1 >> 26) + (uint64(h0) * r2) + (uint64(h1) * r1) + (uint64(h2) * r0) + (uint64(h3) * R4) + (uint64(h4) * R3)
d3 := (d2 >> 26) + (uint64(h0) * r3) + (uint64(h1) * r2) + (uint64(h2) * r1) + (uint64(h3) * r0) + (uint64(h4) * R4)
d4 := (d3 >> 26) + (uint64(h0) * r4) + (uint64(h1) * r3) + (uint64(h2) * r2) + (uint64(h3) * r1) + (uint64(h4) * r0)
// h %= p
h0 = uint32(d0) & 0x3ffffff
h1 = uint32(d1) & 0x3ffffff
h2 = uint32(d2) & 0x3ffffff
h3 = uint32(d3) & 0x3ffffff
h4 = uint32(d4) & 0x3ffffff
h0 += uint32(d4>>26) * 5
h1 += h0 >> 26
h0 = h0 & 0x3ffffff
}
// h %= p reduction
h2 += h1 >> 26
h1 &= 0x3ffffff
h3 += h2 >> 26
h2 &= 0x3ffffff
h4 += h3 >> 26
h3 &= 0x3ffffff
h0 += 5 * (h4 >> 26)
h4 &= 0x3ffffff
h1 += h0 >> 26
h0 &= 0x3ffffff
// h - p
t0 := h0 + 5
t1 := h1 + (t0 >> 26)
t2 := h2 + (t1 >> 26)
t3 := h3 + (t2 >> 26)
t4 := h4 + (t3 >> 26) - (1 << 26)
t0 &= 0x3ffffff
t1 &= 0x3ffffff
t2 &= 0x3ffffff
t3 &= 0x3ffffff
// select h if h < p else h - p
t_mask := (t4 >> 31) - 1
h_mask := ^t_mask
h0 = (h0 & h_mask) | (t0 & t_mask)
h1 = (h1 & h_mask) | (t1 & t_mask)
h2 = (h2 & h_mask) | (t2 & t_mask)
h3 = (h3 & h_mask) | (t3 & t_mask)
h4 = (h4 & h_mask) | (t4 & t_mask)
// h %= 2^128
h0 |= h1 << 26
h1 = ((h1 >> 6) | (h2 << 20))
h2 = ((h2 >> 12) | (h3 << 14))
h3 = ((h3 >> 18) | (h4 << 8))
// s: the s part of the key
// tag = (h + s) % (2^128)
t := uint64(h0) + uint64(binary.LittleEndian.Uint32(key[16:]))
h0 = uint32(t)
t = uint64(h1) + uint64(binary.LittleEndian.Uint32(key[20:])) + (t >> 32)
h1 = uint32(t)
t = uint64(h2) + uint64(binary.LittleEndian.Uint32(key[24:])) + (t >> 32)
h2 = uint32(t)
t = uint64(h3) + uint64(binary.LittleEndian.Uint32(key[28:])) + (t >> 32)
h3 = uint32(t)
binary.LittleEndian.PutUint32(out[0:], h0)
binary.LittleEndian.PutUint32(out[4:], h1)
binary.LittleEndian.PutUint32(out[8:], h2)
binary.LittleEndian.PutUint32(out[12:], h3)
}

View file

@ -0,0 +1,144 @@
// Copyright 2012 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 salsa provides low-level access to functions in the Salsa family.
package salsa // import "golang.org/x/crypto/salsa20/salsa"
// Sigma is the Salsa20 constant for 256-bit keys.
var Sigma = [16]byte{'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', '2', '-', 'b', 'y', 't', 'e', ' ', 'k'}
// HSalsa20 applies the HSalsa20 core function to a 16-byte input in, 32-byte
// key k, and 16-byte constant c, and puts the result into the 32-byte array
// out.
func HSalsa20(out *[32]byte, in *[16]byte, k *[32]byte, c *[16]byte) {
x0 := uint32(c[0]) | uint32(c[1])<<8 | uint32(c[2])<<16 | uint32(c[3])<<24
x1 := uint32(k[0]) | uint32(k[1])<<8 | uint32(k[2])<<16 | uint32(k[3])<<24
x2 := uint32(k[4]) | uint32(k[5])<<8 | uint32(k[6])<<16 | uint32(k[7])<<24
x3 := uint32(k[8]) | uint32(k[9])<<8 | uint32(k[10])<<16 | uint32(k[11])<<24
x4 := uint32(k[12]) | uint32(k[13])<<8 | uint32(k[14])<<16 | uint32(k[15])<<24
x5 := uint32(c[4]) | uint32(c[5])<<8 | uint32(c[6])<<16 | uint32(c[7])<<24
x6 := uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24
x7 := uint32(in[4]) | uint32(in[5])<<8 | uint32(in[6])<<16 | uint32(in[7])<<24
x8 := uint32(in[8]) | uint32(in[9])<<8 | uint32(in[10])<<16 | uint32(in[11])<<24
x9 := uint32(in[12]) | uint32(in[13])<<8 | uint32(in[14])<<16 | uint32(in[15])<<24
x10 := uint32(c[8]) | uint32(c[9])<<8 | uint32(c[10])<<16 | uint32(c[11])<<24
x11 := uint32(k[16]) | uint32(k[17])<<8 | uint32(k[18])<<16 | uint32(k[19])<<24
x12 := uint32(k[20]) | uint32(k[21])<<8 | uint32(k[22])<<16 | uint32(k[23])<<24
x13 := uint32(k[24]) | uint32(k[25])<<8 | uint32(k[26])<<16 | uint32(k[27])<<24
x14 := uint32(k[28]) | uint32(k[29])<<8 | uint32(k[30])<<16 | uint32(k[31])<<24
x15 := uint32(c[12]) | uint32(c[13])<<8 | uint32(c[14])<<16 | uint32(c[15])<<24
for i := 0; i < 20; i += 2 {
u := x0 + x12
x4 ^= u<<7 | u>>(32-7)
u = x4 + x0
x8 ^= u<<9 | u>>(32-9)
u = x8 + x4
x12 ^= u<<13 | u>>(32-13)
u = x12 + x8
x0 ^= u<<18 | u>>(32-18)
u = x5 + x1
x9 ^= u<<7 | u>>(32-7)
u = x9 + x5
x13 ^= u<<9 | u>>(32-9)
u = x13 + x9
x1 ^= u<<13 | u>>(32-13)
u = x1 + x13
x5 ^= u<<18 | u>>(32-18)
u = x10 + x6
x14 ^= u<<7 | u>>(32-7)
u = x14 + x10
x2 ^= u<<9 | u>>(32-9)
u = x2 + x14
x6 ^= u<<13 | u>>(32-13)
u = x6 + x2
x10 ^= u<<18 | u>>(32-18)
u = x15 + x11
x3 ^= u<<7 | u>>(32-7)
u = x3 + x15
x7 ^= u<<9 | u>>(32-9)
u = x7 + x3
x11 ^= u<<13 | u>>(32-13)
u = x11 + x7
x15 ^= u<<18 | u>>(32-18)
u = x0 + x3
x1 ^= u<<7 | u>>(32-7)
u = x1 + x0
x2 ^= u<<9 | u>>(32-9)
u = x2 + x1
x3 ^= u<<13 | u>>(32-13)
u = x3 + x2
x0 ^= u<<18 | u>>(32-18)
u = x5 + x4
x6 ^= u<<7 | u>>(32-7)
u = x6 + x5
x7 ^= u<<9 | u>>(32-9)
u = x7 + x6
x4 ^= u<<13 | u>>(32-13)
u = x4 + x7
x5 ^= u<<18 | u>>(32-18)
u = x10 + x9
x11 ^= u<<7 | u>>(32-7)
u = x11 + x10
x8 ^= u<<9 | u>>(32-9)
u = x8 + x11
x9 ^= u<<13 | u>>(32-13)
u = x9 + x8
x10 ^= u<<18 | u>>(32-18)
u = x15 + x14
x12 ^= u<<7 | u>>(32-7)
u = x12 + x15
x13 ^= u<<9 | u>>(32-9)
u = x13 + x12
x14 ^= u<<13 | u>>(32-13)
u = x14 + x13
x15 ^= u<<18 | u>>(32-18)
}
out[0] = byte(x0)
out[1] = byte(x0 >> 8)
out[2] = byte(x0 >> 16)
out[3] = byte(x0 >> 24)
out[4] = byte(x5)
out[5] = byte(x5 >> 8)
out[6] = byte(x5 >> 16)
out[7] = byte(x5 >> 24)
out[8] = byte(x10)
out[9] = byte(x10 >> 8)
out[10] = byte(x10 >> 16)
out[11] = byte(x10 >> 24)
out[12] = byte(x15)
out[13] = byte(x15 >> 8)
out[14] = byte(x15 >> 16)
out[15] = byte(x15 >> 24)
out[16] = byte(x6)
out[17] = byte(x6 >> 8)
out[18] = byte(x6 >> 16)
out[19] = byte(x6 >> 24)
out[20] = byte(x7)
out[21] = byte(x7 >> 8)
out[22] = byte(x7 >> 16)
out[23] = byte(x7 >> 24)
out[24] = byte(x8)
out[25] = byte(x8 >> 8)
out[26] = byte(x8 >> 16)
out[27] = byte(x8 >> 24)
out[28] = byte(x9)
out[29] = byte(x9 >> 8)
out[30] = byte(x9 >> 16)
out[31] = byte(x9 >> 24)
}

View file

@ -0,0 +1,889 @@
// Copyright 2012 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.
// +build amd64,!appengine,!gccgo
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
// func salsa2020XORKeyStream(out, in *byte, n uint64, nonce, key *byte)
// This needs up to 64 bytes at 360(SP); hence the non-obvious frame size.
TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVQ out+0(FP),DI
MOVQ in+8(FP),SI
MOVQ n+16(FP),DX
MOVQ nonce+24(FP),CX
MOVQ key+32(FP),R8
MOVQ SP,R12
MOVQ SP,R9
ADDQ $31, R9
ANDQ $~31, R9
MOVQ R9, SP
MOVQ DX,R9
MOVQ CX,DX
MOVQ R8,R10
CMPQ R9,$0
JBE DONE
START:
MOVL 20(R10),CX
MOVL 0(R10),R8
MOVL 0(DX),AX
MOVL 16(R10),R11
MOVL CX,0(SP)
MOVL R8, 4 (SP)
MOVL AX, 8 (SP)
MOVL R11, 12 (SP)
MOVL 8(DX),CX
MOVL 24(R10),R8
MOVL 4(R10),AX
MOVL 4(DX),R11
MOVL CX,16(SP)
MOVL R8, 20 (SP)
MOVL AX, 24 (SP)
MOVL R11, 28 (SP)
MOVL 12(DX),CX
MOVL 12(R10),DX
MOVL 28(R10),R8
MOVL 8(R10),AX
MOVL DX,32(SP)
MOVL CX, 36 (SP)
MOVL R8, 40 (SP)
MOVL AX, 44 (SP)
MOVQ $1634760805,DX
MOVQ $857760878,CX
MOVQ $2036477234,R8
MOVQ $1797285236,AX
MOVL DX,48(SP)
MOVL CX, 52 (SP)
MOVL R8, 56 (SP)
MOVL AX, 60 (SP)
CMPQ R9,$256
JB BYTESBETWEEN1AND255
MOVOA 48(SP),X0
PSHUFL $0X55,X0,X1
PSHUFL $0XAA,X0,X2
PSHUFL $0XFF,X0,X3
PSHUFL $0X00,X0,X0
MOVOA X1,64(SP)
MOVOA X2,80(SP)
MOVOA X3,96(SP)
MOVOA X0,112(SP)
MOVOA 0(SP),X0
PSHUFL $0XAA,X0,X1
PSHUFL $0XFF,X0,X2
PSHUFL $0X00,X0,X3
PSHUFL $0X55,X0,X0
MOVOA X1,128(SP)
MOVOA X2,144(SP)
MOVOA X3,160(SP)
MOVOA X0,176(SP)
MOVOA 16(SP),X0
PSHUFL $0XFF,X0,X1
PSHUFL $0X55,X0,X2
PSHUFL $0XAA,X0,X0
MOVOA X1,192(SP)
MOVOA X2,208(SP)
MOVOA X0,224(SP)
MOVOA 32(SP),X0
PSHUFL $0X00,X0,X1
PSHUFL $0XAA,X0,X2
PSHUFL $0XFF,X0,X0
MOVOA X1,240(SP)
MOVOA X2,256(SP)
MOVOA X0,272(SP)
BYTESATLEAST256:
MOVL 16(SP),DX
MOVL 36 (SP),CX
MOVL DX,288(SP)
MOVL CX,304(SP)
ADDQ $1,DX
SHLQ $32,CX
ADDQ CX,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX, 292 (SP)
MOVL CX, 308 (SP)
ADDQ $1,DX
SHLQ $32,CX
ADDQ CX,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX, 296 (SP)
MOVL CX, 312 (SP)
ADDQ $1,DX
SHLQ $32,CX
ADDQ CX,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX, 300 (SP)
MOVL CX, 316 (SP)
ADDQ $1,DX
SHLQ $32,CX
ADDQ CX,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX,16(SP)
MOVL CX, 36 (SP)
MOVQ R9,352(SP)
MOVQ $20,DX
MOVOA 64(SP),X0
MOVOA 80(SP),X1
MOVOA 96(SP),X2
MOVOA 256(SP),X3
MOVOA 272(SP),X4
MOVOA 128(SP),X5
MOVOA 144(SP),X6
MOVOA 176(SP),X7
MOVOA 192(SP),X8
MOVOA 208(SP),X9
MOVOA 224(SP),X10
MOVOA 304(SP),X11
MOVOA 112(SP),X12
MOVOA 160(SP),X13
MOVOA 240(SP),X14
MOVOA 288(SP),X15
MAINLOOP1:
MOVOA X1,320(SP)
MOVOA X2,336(SP)
MOVOA X13,X1
PADDL X12,X1
MOVOA X1,X2
PSLLL $7,X1
PXOR X1,X14
PSRLL $25,X2
PXOR X2,X14
MOVOA X7,X1
PADDL X0,X1
MOVOA X1,X2
PSLLL $7,X1
PXOR X1,X11
PSRLL $25,X2
PXOR X2,X11
MOVOA X12,X1
PADDL X14,X1
MOVOA X1,X2
PSLLL $9,X1
PXOR X1,X15
PSRLL $23,X2
PXOR X2,X15
MOVOA X0,X1
PADDL X11,X1
MOVOA X1,X2
PSLLL $9,X1
PXOR X1,X9
PSRLL $23,X2
PXOR X2,X9
MOVOA X14,X1
PADDL X15,X1
MOVOA X1,X2
PSLLL $13,X1
PXOR X1,X13
PSRLL $19,X2
PXOR X2,X13
MOVOA X11,X1
PADDL X9,X1
MOVOA X1,X2
PSLLL $13,X1
PXOR X1,X7
PSRLL $19,X2
PXOR X2,X7
MOVOA X15,X1
PADDL X13,X1
MOVOA X1,X2
PSLLL $18,X1
PXOR X1,X12
PSRLL $14,X2
PXOR X2,X12
MOVOA 320(SP),X1
MOVOA X12,320(SP)
MOVOA X9,X2
PADDL X7,X2
MOVOA X2,X12
PSLLL $18,X2
PXOR X2,X0
PSRLL $14,X12
PXOR X12,X0
MOVOA X5,X2
PADDL X1,X2
MOVOA X2,X12
PSLLL $7,X2
PXOR X2,X3
PSRLL $25,X12
PXOR X12,X3
MOVOA 336(SP),X2
MOVOA X0,336(SP)
MOVOA X6,X0
PADDL X2,X0
MOVOA X0,X12
PSLLL $7,X0
PXOR X0,X4
PSRLL $25,X12
PXOR X12,X4
MOVOA X1,X0
PADDL X3,X0
MOVOA X0,X12
PSLLL $9,X0
PXOR X0,X10
PSRLL $23,X12
PXOR X12,X10
MOVOA X2,X0
PADDL X4,X0
MOVOA X0,X12
PSLLL $9,X0
PXOR X0,X8
PSRLL $23,X12
PXOR X12,X8
MOVOA X3,X0
PADDL X10,X0
MOVOA X0,X12
PSLLL $13,X0
PXOR X0,X5
PSRLL $19,X12
PXOR X12,X5
MOVOA X4,X0
PADDL X8,X0
MOVOA X0,X12
PSLLL $13,X0
PXOR X0,X6
PSRLL $19,X12
PXOR X12,X6
MOVOA X10,X0
PADDL X5,X0
MOVOA X0,X12
PSLLL $18,X0
PXOR X0,X1
PSRLL $14,X12
PXOR X12,X1
MOVOA 320(SP),X0
MOVOA X1,320(SP)
MOVOA X4,X1
PADDL X0,X1
MOVOA X1,X12
PSLLL $7,X1
PXOR X1,X7
PSRLL $25,X12
PXOR X12,X7
MOVOA X8,X1
PADDL X6,X1
MOVOA X1,X12
PSLLL $18,X1
PXOR X1,X2
PSRLL $14,X12
PXOR X12,X2
MOVOA 336(SP),X12
MOVOA X2,336(SP)
MOVOA X14,X1
PADDL X12,X1
MOVOA X1,X2
PSLLL $7,X1
PXOR X1,X5
PSRLL $25,X2
PXOR X2,X5
MOVOA X0,X1
PADDL X7,X1
MOVOA X1,X2
PSLLL $9,X1
PXOR X1,X10
PSRLL $23,X2
PXOR X2,X10
MOVOA X12,X1
PADDL X5,X1
MOVOA X1,X2
PSLLL $9,X1
PXOR X1,X8
PSRLL $23,X2
PXOR X2,X8
MOVOA X7,X1
PADDL X10,X1
MOVOA X1,X2
PSLLL $13,X1
PXOR X1,X4
PSRLL $19,X2
PXOR X2,X4
MOVOA X5,X1
PADDL X8,X1
MOVOA X1,X2
PSLLL $13,X1
PXOR X1,X14
PSRLL $19,X2
PXOR X2,X14
MOVOA X10,X1
PADDL X4,X1
MOVOA X1,X2
PSLLL $18,X1
PXOR X1,X0
PSRLL $14,X2
PXOR X2,X0
MOVOA 320(SP),X1
MOVOA X0,320(SP)
MOVOA X8,X0
PADDL X14,X0
MOVOA X0,X2
PSLLL $18,X0
PXOR X0,X12
PSRLL $14,X2
PXOR X2,X12
MOVOA X11,X0
PADDL X1,X0
MOVOA X0,X2
PSLLL $7,X0
PXOR X0,X6
PSRLL $25,X2
PXOR X2,X6
MOVOA 336(SP),X2
MOVOA X12,336(SP)
MOVOA X3,X0
PADDL X2,X0
MOVOA X0,X12
PSLLL $7,X0
PXOR X0,X13
PSRLL $25,X12
PXOR X12,X13
MOVOA X1,X0
PADDL X6,X0
MOVOA X0,X12
PSLLL $9,X0
PXOR X0,X15
PSRLL $23,X12
PXOR X12,X15
MOVOA X2,X0
PADDL X13,X0
MOVOA X0,X12
PSLLL $9,X0
PXOR X0,X9
PSRLL $23,X12
PXOR X12,X9
MOVOA X6,X0
PADDL X15,X0
MOVOA X0,X12
PSLLL $13,X0
PXOR X0,X11
PSRLL $19,X12
PXOR X12,X11
MOVOA X13,X0
PADDL X9,X0
MOVOA X0,X12
PSLLL $13,X0
PXOR X0,X3
PSRLL $19,X12
PXOR X12,X3
MOVOA X15,X0
PADDL X11,X0
MOVOA X0,X12
PSLLL $18,X0
PXOR X0,X1
PSRLL $14,X12
PXOR X12,X1
MOVOA X9,X0
PADDL X3,X0
MOVOA X0,X12
PSLLL $18,X0
PXOR X0,X2
PSRLL $14,X12
PXOR X12,X2
MOVOA 320(SP),X12
MOVOA 336(SP),X0
SUBQ $2,DX
JA MAINLOOP1
PADDL 112(SP),X12
PADDL 176(SP),X7
PADDL 224(SP),X10
PADDL 272(SP),X4
MOVD X12,DX
MOVD X7,CX
MOVD X10,R8
MOVD X4,R9
PSHUFL $0X39,X12,X12
PSHUFL $0X39,X7,X7
PSHUFL $0X39,X10,X10
PSHUFL $0X39,X4,X4
XORL 0(SI),DX
XORL 4(SI),CX
XORL 8(SI),R8
XORL 12(SI),R9
MOVL DX,0(DI)
MOVL CX,4(DI)
MOVL R8,8(DI)
MOVL R9,12(DI)
MOVD X12,DX
MOVD X7,CX
MOVD X10,R8
MOVD X4,R9
PSHUFL $0X39,X12,X12
PSHUFL $0X39,X7,X7
PSHUFL $0X39,X10,X10
PSHUFL $0X39,X4,X4
XORL 64(SI),DX
XORL 68(SI),CX
XORL 72(SI),R8
XORL 76(SI),R9
MOVL DX,64(DI)
MOVL CX,68(DI)
MOVL R8,72(DI)
MOVL R9,76(DI)
MOVD X12,DX
MOVD X7,CX
MOVD X10,R8
MOVD X4,R9
PSHUFL $0X39,X12,X12
PSHUFL $0X39,X7,X7
PSHUFL $0X39,X10,X10
PSHUFL $0X39,X4,X4
XORL 128(SI),DX
XORL 132(SI),CX
XORL 136(SI),R8
XORL 140(SI),R9
MOVL DX,128(DI)
MOVL CX,132(DI)
MOVL R8,136(DI)
MOVL R9,140(DI)
MOVD X12,DX
MOVD X7,CX
MOVD X10,R8
MOVD X4,R9
XORL 192(SI),DX
XORL 196(SI),CX
XORL 200(SI),R8
XORL 204(SI),R9
MOVL DX,192(DI)
MOVL CX,196(DI)
MOVL R8,200(DI)
MOVL R9,204(DI)
PADDL 240(SP),X14
PADDL 64(SP),X0
PADDL 128(SP),X5
PADDL 192(SP),X8
MOVD X14,DX
MOVD X0,CX
MOVD X5,R8
MOVD X8,R9
PSHUFL $0X39,X14,X14
PSHUFL $0X39,X0,X0
PSHUFL $0X39,X5,X5
PSHUFL $0X39,X8,X8
XORL 16(SI),DX
XORL 20(SI),CX
XORL 24(SI),R8
XORL 28(SI),R9
MOVL DX,16(DI)
MOVL CX,20(DI)
MOVL R8,24(DI)
MOVL R9,28(DI)
MOVD X14,DX
MOVD X0,CX
MOVD X5,R8
MOVD X8,R9
PSHUFL $0X39,X14,X14
PSHUFL $0X39,X0,X0
PSHUFL $0X39,X5,X5
PSHUFL $0X39,X8,X8
XORL 80(SI),DX
XORL 84(SI),CX
XORL 88(SI),R8
XORL 92(SI),R9
MOVL DX,80(DI)
MOVL CX,84(DI)
MOVL R8,88(DI)
MOVL R9,92(DI)
MOVD X14,DX
MOVD X0,CX
MOVD X5,R8
MOVD X8,R9
PSHUFL $0X39,X14,X14
PSHUFL $0X39,X0,X0
PSHUFL $0X39,X5,X5
PSHUFL $0X39,X8,X8
XORL 144(SI),DX
XORL 148(SI),CX
XORL 152(SI),R8
XORL 156(SI),R9
MOVL DX,144(DI)
MOVL CX,148(DI)
MOVL R8,152(DI)
MOVL R9,156(DI)
MOVD X14,DX
MOVD X0,CX
MOVD X5,R8
MOVD X8,R9
XORL 208(SI),DX
XORL 212(SI),CX
XORL 216(SI),R8
XORL 220(SI),R9
MOVL DX,208(DI)
MOVL CX,212(DI)
MOVL R8,216(DI)
MOVL R9,220(DI)
PADDL 288(SP),X15
PADDL 304(SP),X11
PADDL 80(SP),X1
PADDL 144(SP),X6
MOVD X15,DX
MOVD X11,CX
MOVD X1,R8
MOVD X6,R9
PSHUFL $0X39,X15,X15
PSHUFL $0X39,X11,X11
PSHUFL $0X39,X1,X1
PSHUFL $0X39,X6,X6
XORL 32(SI),DX
XORL 36(SI),CX
XORL 40(SI),R8
XORL 44(SI),R9
MOVL DX,32(DI)
MOVL CX,36(DI)
MOVL R8,40(DI)
MOVL R9,44(DI)
MOVD X15,DX
MOVD X11,CX
MOVD X1,R8
MOVD X6,R9
PSHUFL $0X39,X15,X15
PSHUFL $0X39,X11,X11
PSHUFL $0X39,X1,X1
PSHUFL $0X39,X6,X6
XORL 96(SI),DX
XORL 100(SI),CX
XORL 104(SI),R8
XORL 108(SI),R9
MOVL DX,96(DI)
MOVL CX,100(DI)
MOVL R8,104(DI)
MOVL R9,108(DI)
MOVD X15,DX
MOVD X11,CX
MOVD X1,R8
MOVD X6,R9
PSHUFL $0X39,X15,X15
PSHUFL $0X39,X11,X11
PSHUFL $0X39,X1,X1
PSHUFL $0X39,X6,X6
XORL 160(SI),DX
XORL 164(SI),CX
XORL 168(SI),R8
XORL 172(SI),R9
MOVL DX,160(DI)
MOVL CX,164(DI)
MOVL R8,168(DI)
MOVL R9,172(DI)
MOVD X15,DX
MOVD X11,CX
MOVD X1,R8
MOVD X6,R9
XORL 224(SI),DX
XORL 228(SI),CX
XORL 232(SI),R8
XORL 236(SI),R9
MOVL DX,224(DI)
MOVL CX,228(DI)
MOVL R8,232(DI)
MOVL R9,236(DI)
PADDL 160(SP),X13
PADDL 208(SP),X9
PADDL 256(SP),X3
PADDL 96(SP),X2
MOVD X13,DX
MOVD X9,CX
MOVD X3,R8
MOVD X2,R9
PSHUFL $0X39,X13,X13
PSHUFL $0X39,X9,X9
PSHUFL $0X39,X3,X3
PSHUFL $0X39,X2,X2
XORL 48(SI),DX
XORL 52(SI),CX
XORL 56(SI),R8
XORL 60(SI),R9
MOVL DX,48(DI)
MOVL CX,52(DI)
MOVL R8,56(DI)
MOVL R9,60(DI)
MOVD X13,DX
MOVD X9,CX
MOVD X3,R8
MOVD X2,R9
PSHUFL $0X39,X13,X13
PSHUFL $0X39,X9,X9
PSHUFL $0X39,X3,X3
PSHUFL $0X39,X2,X2
XORL 112(SI),DX
XORL 116(SI),CX
XORL 120(SI),R8
XORL 124(SI),R9
MOVL DX,112(DI)
MOVL CX,116(DI)
MOVL R8,120(DI)
MOVL R9,124(DI)
MOVD X13,DX
MOVD X9,CX
MOVD X3,R8
MOVD X2,R9
PSHUFL $0X39,X13,X13
PSHUFL $0X39,X9,X9
PSHUFL $0X39,X3,X3
PSHUFL $0X39,X2,X2
XORL 176(SI),DX
XORL 180(SI),CX
XORL 184(SI),R8
XORL 188(SI),R9
MOVL DX,176(DI)
MOVL CX,180(DI)
MOVL R8,184(DI)
MOVL R9,188(DI)
MOVD X13,DX
MOVD X9,CX
MOVD X3,R8
MOVD X2,R9
XORL 240(SI),DX
XORL 244(SI),CX
XORL 248(SI),R8
XORL 252(SI),R9
MOVL DX,240(DI)
MOVL CX,244(DI)
MOVL R8,248(DI)
MOVL R9,252(DI)
MOVQ 352(SP),R9
SUBQ $256,R9
ADDQ $256,SI
ADDQ $256,DI
CMPQ R9,$256
JAE BYTESATLEAST256
CMPQ R9,$0
JBE DONE
BYTESBETWEEN1AND255:
CMPQ R9,$64
JAE NOCOPY
MOVQ DI,DX
LEAQ 360(SP),DI
MOVQ R9,CX
REP; MOVSB
LEAQ 360(SP),DI
LEAQ 360(SP),SI
NOCOPY:
MOVQ R9,352(SP)
MOVOA 48(SP),X0
MOVOA 0(SP),X1
MOVOA 16(SP),X2
MOVOA 32(SP),X3
MOVOA X1,X4
MOVQ $20,CX
MAINLOOP2:
PADDL X0,X4
MOVOA X0,X5
MOVOA X4,X6
PSLLL $7,X4
PSRLL $25,X6
PXOR X4,X3
PXOR X6,X3
PADDL X3,X5
MOVOA X3,X4
MOVOA X5,X6
PSLLL $9,X5
PSRLL $23,X6
PXOR X5,X2
PSHUFL $0X93,X3,X3
PXOR X6,X2
PADDL X2,X4
MOVOA X2,X5
MOVOA X4,X6
PSLLL $13,X4
PSRLL $19,X6
PXOR X4,X1
PSHUFL $0X4E,X2,X2
PXOR X6,X1
PADDL X1,X5
MOVOA X3,X4
MOVOA X5,X6
PSLLL $18,X5
PSRLL $14,X6
PXOR X5,X0
PSHUFL $0X39,X1,X1
PXOR X6,X0
PADDL X0,X4
MOVOA X0,X5
MOVOA X4,X6
PSLLL $7,X4
PSRLL $25,X6
PXOR X4,X1
PXOR X6,X1
PADDL X1,X5
MOVOA X1,X4
MOVOA X5,X6
PSLLL $9,X5
PSRLL $23,X6
PXOR X5,X2
PSHUFL $0X93,X1,X1
PXOR X6,X2
PADDL X2,X4
MOVOA X2,X5
MOVOA X4,X6
PSLLL $13,X4
PSRLL $19,X6
PXOR X4,X3
PSHUFL $0X4E,X2,X2
PXOR X6,X3
PADDL X3,X5
MOVOA X1,X4
MOVOA X5,X6
PSLLL $18,X5
PSRLL $14,X6
PXOR X5,X0
PSHUFL $0X39,X3,X3
PXOR X6,X0
PADDL X0,X4
MOVOA X0,X5
MOVOA X4,X6
PSLLL $7,X4
PSRLL $25,X6
PXOR X4,X3
PXOR X6,X3
PADDL X3,X5
MOVOA X3,X4
MOVOA X5,X6
PSLLL $9,X5
PSRLL $23,X6
PXOR X5,X2
PSHUFL $0X93,X3,X3
PXOR X6,X2
PADDL X2,X4
MOVOA X2,X5
MOVOA X4,X6
PSLLL $13,X4
PSRLL $19,X6
PXOR X4,X1
PSHUFL $0X4E,X2,X2
PXOR X6,X1
PADDL X1,X5
MOVOA X3,X4
MOVOA X5,X6
PSLLL $18,X5
PSRLL $14,X6
PXOR X5,X0
PSHUFL $0X39,X1,X1
PXOR X6,X0
PADDL X0,X4
MOVOA X0,X5
MOVOA X4,X6
PSLLL $7,X4
PSRLL $25,X6
PXOR X4,X1
PXOR X6,X1
PADDL X1,X5
MOVOA X1,X4
MOVOA X5,X6
PSLLL $9,X5
PSRLL $23,X6
PXOR X5,X2
PSHUFL $0X93,X1,X1
PXOR X6,X2
PADDL X2,X4
MOVOA X2,X5
MOVOA X4,X6
PSLLL $13,X4
PSRLL $19,X6
PXOR X4,X3
PSHUFL $0X4E,X2,X2
PXOR X6,X3
SUBQ $4,CX
PADDL X3,X5
MOVOA X1,X4
MOVOA X5,X6
PSLLL $18,X5
PXOR X7,X7
PSRLL $14,X6
PXOR X5,X0
PSHUFL $0X39,X3,X3
PXOR X6,X0
JA MAINLOOP2
PADDL 48(SP),X0
PADDL 0(SP),X1
PADDL 16(SP),X2
PADDL 32(SP),X3
MOVD X0,CX
MOVD X1,R8
MOVD X2,R9
MOVD X3,AX
PSHUFL $0X39,X0,X0
PSHUFL $0X39,X1,X1
PSHUFL $0X39,X2,X2
PSHUFL $0X39,X3,X3
XORL 0(SI),CX
XORL 48(SI),R8
XORL 32(SI),R9
XORL 16(SI),AX
MOVL CX,0(DI)
MOVL R8,48(DI)
MOVL R9,32(DI)
MOVL AX,16(DI)
MOVD X0,CX
MOVD X1,R8
MOVD X2,R9
MOVD X3,AX
PSHUFL $0X39,X0,X0
PSHUFL $0X39,X1,X1
PSHUFL $0X39,X2,X2
PSHUFL $0X39,X3,X3
XORL 20(SI),CX
XORL 4(SI),R8
XORL 52(SI),R9
XORL 36(SI),AX
MOVL CX,20(DI)
MOVL R8,4(DI)
MOVL R9,52(DI)
MOVL AX,36(DI)
MOVD X0,CX
MOVD X1,R8
MOVD X2,R9
MOVD X3,AX
PSHUFL $0X39,X0,X0
PSHUFL $0X39,X1,X1
PSHUFL $0X39,X2,X2
PSHUFL $0X39,X3,X3
XORL 40(SI),CX
XORL 24(SI),R8
XORL 8(SI),R9
XORL 56(SI),AX
MOVL CX,40(DI)
MOVL R8,24(DI)
MOVL R9,8(DI)
MOVL AX,56(DI)
MOVD X0,CX
MOVD X1,R8
MOVD X2,R9
MOVD X3,AX
XORL 60(SI),CX
XORL 44(SI),R8
XORL 28(SI),R9
XORL 12(SI),AX
MOVL CX,60(DI)
MOVL R8,44(DI)
MOVL R9,28(DI)
MOVL AX,12(DI)
MOVQ 352(SP),R9
MOVL 16(SP),CX
MOVL 36 (SP),R8
ADDQ $1,CX
SHLQ $32,R8
ADDQ R8,CX
MOVQ CX,R8
SHRQ $32,R8
MOVL CX,16(SP)
MOVL R8, 36 (SP)
CMPQ R9,$64
JA BYTESATLEAST65
JAE BYTESATLEAST64
MOVQ DI,SI
MOVQ DX,DI
MOVQ R9,CX
REP; MOVSB
BYTESATLEAST64:
DONE:
MOVQ R12,SP
RET
BYTESATLEAST65:
SUBQ $64,R9
ADDQ $64,DI
ADDQ $64,SI
JMP BYTESBETWEEN1AND255

View file

@ -0,0 +1,199 @@
// Copyright 2012 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 salsa
// Core208 applies the Salsa20/8 core function to the 64-byte array in and puts
// the result into the 64-byte array out. The input and output may be the same array.
func Core208(out *[64]byte, in *[64]byte) {
j0 := uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24
j1 := uint32(in[4]) | uint32(in[5])<<8 | uint32(in[6])<<16 | uint32(in[7])<<24
j2 := uint32(in[8]) | uint32(in[9])<<8 | uint32(in[10])<<16 | uint32(in[11])<<24
j3 := uint32(in[12]) | uint32(in[13])<<8 | uint32(in[14])<<16 | uint32(in[15])<<24
j4 := uint32(in[16]) | uint32(in[17])<<8 | uint32(in[18])<<16 | uint32(in[19])<<24
j5 := uint32(in[20]) | uint32(in[21])<<8 | uint32(in[22])<<16 | uint32(in[23])<<24
j6 := uint32(in[24]) | uint32(in[25])<<8 | uint32(in[26])<<16 | uint32(in[27])<<24
j7 := uint32(in[28]) | uint32(in[29])<<8 | uint32(in[30])<<16 | uint32(in[31])<<24
j8 := uint32(in[32]) | uint32(in[33])<<8 | uint32(in[34])<<16 | uint32(in[35])<<24
j9 := uint32(in[36]) | uint32(in[37])<<8 | uint32(in[38])<<16 | uint32(in[39])<<24
j10 := uint32(in[40]) | uint32(in[41])<<8 | uint32(in[42])<<16 | uint32(in[43])<<24
j11 := uint32(in[44]) | uint32(in[45])<<8 | uint32(in[46])<<16 | uint32(in[47])<<24
j12 := uint32(in[48]) | uint32(in[49])<<8 | uint32(in[50])<<16 | uint32(in[51])<<24
j13 := uint32(in[52]) | uint32(in[53])<<8 | uint32(in[54])<<16 | uint32(in[55])<<24
j14 := uint32(in[56]) | uint32(in[57])<<8 | uint32(in[58])<<16 | uint32(in[59])<<24
j15 := uint32(in[60]) | uint32(in[61])<<8 | uint32(in[62])<<16 | uint32(in[63])<<24
x0, x1, x2, x3, x4, x5, x6, x7, x8 := j0, j1, j2, j3, j4, j5, j6, j7, j8
x9, x10, x11, x12, x13, x14, x15 := j9, j10, j11, j12, j13, j14, j15
for i := 0; i < 8; i += 2 {
u := x0 + x12
x4 ^= u<<7 | u>>(32-7)
u = x4 + x0
x8 ^= u<<9 | u>>(32-9)
u = x8 + x4
x12 ^= u<<13 | u>>(32-13)
u = x12 + x8
x0 ^= u<<18 | u>>(32-18)
u = x5 + x1
x9 ^= u<<7 | u>>(32-7)
u = x9 + x5
x13 ^= u<<9 | u>>(32-9)
u = x13 + x9
x1 ^= u<<13 | u>>(32-13)
u = x1 + x13
x5 ^= u<<18 | u>>(32-18)
u = x10 + x6
x14 ^= u<<7 | u>>(32-7)
u = x14 + x10
x2 ^= u<<9 | u>>(32-9)
u = x2 + x14
x6 ^= u<<13 | u>>(32-13)
u = x6 + x2
x10 ^= u<<18 | u>>(32-18)
u = x15 + x11
x3 ^= u<<7 | u>>(32-7)
u = x3 + x15
x7 ^= u<<9 | u>>(32-9)
u = x7 + x3
x11 ^= u<<13 | u>>(32-13)
u = x11 + x7
x15 ^= u<<18 | u>>(32-18)
u = x0 + x3
x1 ^= u<<7 | u>>(32-7)
u = x1 + x0
x2 ^= u<<9 | u>>(32-9)
u = x2 + x1
x3 ^= u<<13 | u>>(32-13)
u = x3 + x2
x0 ^= u<<18 | u>>(32-18)
u = x5 + x4
x6 ^= u<<7 | u>>(32-7)
u = x6 + x5
x7 ^= u<<9 | u>>(32-9)
u = x7 + x6
x4 ^= u<<13 | u>>(32-13)
u = x4 + x7
x5 ^= u<<18 | u>>(32-18)
u = x10 + x9
x11 ^= u<<7 | u>>(32-7)
u = x11 + x10
x8 ^= u<<9 | u>>(32-9)
u = x8 + x11
x9 ^= u<<13 | u>>(32-13)
u = x9 + x8
x10 ^= u<<18 | u>>(32-18)
u = x15 + x14
x12 ^= u<<7 | u>>(32-7)
u = x12 + x15
x13 ^= u<<9 | u>>(32-9)
u = x13 + x12
x14 ^= u<<13 | u>>(32-13)
u = x14 + x13
x15 ^= u<<18 | u>>(32-18)
}
x0 += j0
x1 += j1
x2 += j2
x3 += j3
x4 += j4
x5 += j5
x6 += j6
x7 += j7
x8 += j8
x9 += j9
x10 += j10
x11 += j11
x12 += j12
x13 += j13
x14 += j14
x15 += j15
out[0] = byte(x0)
out[1] = byte(x0 >> 8)
out[2] = byte(x0 >> 16)
out[3] = byte(x0 >> 24)
out[4] = byte(x1)
out[5] = byte(x1 >> 8)
out[6] = byte(x1 >> 16)
out[7] = byte(x1 >> 24)
out[8] = byte(x2)
out[9] = byte(x2 >> 8)
out[10] = byte(x2 >> 16)
out[11] = byte(x2 >> 24)
out[12] = byte(x3)
out[13] = byte(x3 >> 8)
out[14] = byte(x3 >> 16)
out[15] = byte(x3 >> 24)
out[16] = byte(x4)
out[17] = byte(x4 >> 8)
out[18] = byte(x4 >> 16)
out[19] = byte(x4 >> 24)
out[20] = byte(x5)
out[21] = byte(x5 >> 8)
out[22] = byte(x5 >> 16)
out[23] = byte(x5 >> 24)
out[24] = byte(x6)
out[25] = byte(x6 >> 8)
out[26] = byte(x6 >> 16)
out[27] = byte(x6 >> 24)
out[28] = byte(x7)
out[29] = byte(x7 >> 8)
out[30] = byte(x7 >> 16)
out[31] = byte(x7 >> 24)
out[32] = byte(x8)
out[33] = byte(x8 >> 8)
out[34] = byte(x8 >> 16)
out[35] = byte(x8 >> 24)
out[36] = byte(x9)
out[37] = byte(x9 >> 8)
out[38] = byte(x9 >> 16)
out[39] = byte(x9 >> 24)
out[40] = byte(x10)
out[41] = byte(x10 >> 8)
out[42] = byte(x10 >> 16)
out[43] = byte(x10 >> 24)
out[44] = byte(x11)
out[45] = byte(x11 >> 8)
out[46] = byte(x11 >> 16)
out[47] = byte(x11 >> 24)
out[48] = byte(x12)
out[49] = byte(x12 >> 8)
out[50] = byte(x12 >> 16)
out[51] = byte(x12 >> 24)
out[52] = byte(x13)
out[53] = byte(x13 >> 8)
out[54] = byte(x13 >> 16)
out[55] = byte(x13 >> 24)
out[56] = byte(x14)
out[57] = byte(x14 >> 8)
out[58] = byte(x14 >> 16)
out[59] = byte(x14 >> 24)
out[60] = byte(x15)
out[61] = byte(x15 >> 8)
out[62] = byte(x15 >> 16)
out[63] = byte(x15 >> 24)
}

View file

@ -0,0 +1,24 @@
// Copyright 2012 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.
// +build amd64,!appengine,!gccgo
package salsa
// This function is implemented in salsa2020_amd64.s.
//go:noescape
func salsa2020XORKeyStream(out, in *byte, n uint64, nonce, key *byte)
// XORKeyStream crypts bytes from in to out using the given key and counters.
// In and out must overlap entirely or not at all. Counter
// contains the raw salsa20 counter bytes (both nonce and block counter).
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
if len(in) == 0 {
return
}
_ = out[len(in)-1]
salsa2020XORKeyStream(&out[0], &in[0], uint64(len(in)), &counter[0], &key[0])
}

View file

@ -0,0 +1,234 @@
// Copyright 2012 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.
// +build !amd64 appengine gccgo
package salsa
const rounds = 20
// core applies the Salsa20 core function to 16-byte input in, 32-byte key k,
// and 16-byte constant c, and puts the result into 64-byte array out.
func core(out *[64]byte, in *[16]byte, k *[32]byte, c *[16]byte) {
j0 := uint32(c[0]) | uint32(c[1])<<8 | uint32(c[2])<<16 | uint32(c[3])<<24
j1 := uint32(k[0]) | uint32(k[1])<<8 | uint32(k[2])<<16 | uint32(k[3])<<24
j2 := uint32(k[4]) | uint32(k[5])<<8 | uint32(k[6])<<16 | uint32(k[7])<<24
j3 := uint32(k[8]) | uint32(k[9])<<8 | uint32(k[10])<<16 | uint32(k[11])<<24
j4 := uint32(k[12]) | uint32(k[13])<<8 | uint32(k[14])<<16 | uint32(k[15])<<24
j5 := uint32(c[4]) | uint32(c[5])<<8 | uint32(c[6])<<16 | uint32(c[7])<<24
j6 := uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24
j7 := uint32(in[4]) | uint32(in[5])<<8 | uint32(in[6])<<16 | uint32(in[7])<<24
j8 := uint32(in[8]) | uint32(in[9])<<8 | uint32(in[10])<<16 | uint32(in[11])<<24
j9 := uint32(in[12]) | uint32(in[13])<<8 | uint32(in[14])<<16 | uint32(in[15])<<24
j10 := uint32(c[8]) | uint32(c[9])<<8 | uint32(c[10])<<16 | uint32(c[11])<<24
j11 := uint32(k[16]) | uint32(k[17])<<8 | uint32(k[18])<<16 | uint32(k[19])<<24
j12 := uint32(k[20]) | uint32(k[21])<<8 | uint32(k[22])<<16 | uint32(k[23])<<24
j13 := uint32(k[24]) | uint32(k[25])<<8 | uint32(k[26])<<16 | uint32(k[27])<<24
j14 := uint32(k[28]) | uint32(k[29])<<8 | uint32(k[30])<<16 | uint32(k[31])<<24
j15 := uint32(c[12]) | uint32(c[13])<<8 | uint32(c[14])<<16 | uint32(c[15])<<24
x0, x1, x2, x3, x4, x5, x6, x7, x8 := j0, j1, j2, j3, j4, j5, j6, j7, j8
x9, x10, x11, x12, x13, x14, x15 := j9, j10, j11, j12, j13, j14, j15
for i := 0; i < rounds; i += 2 {
u := x0 + x12
x4 ^= u<<7 | u>>(32-7)
u = x4 + x0
x8 ^= u<<9 | u>>(32-9)
u = x8 + x4
x12 ^= u<<13 | u>>(32-13)
u = x12 + x8
x0 ^= u<<18 | u>>(32-18)
u = x5 + x1
x9 ^= u<<7 | u>>(32-7)
u = x9 + x5
x13 ^= u<<9 | u>>(32-9)
u = x13 + x9
x1 ^= u<<13 | u>>(32-13)
u = x1 + x13
x5 ^= u<<18 | u>>(32-18)
u = x10 + x6
x14 ^= u<<7 | u>>(32-7)
u = x14 + x10
x2 ^= u<<9 | u>>(32-9)
u = x2 + x14
x6 ^= u<<13 | u>>(32-13)
u = x6 + x2
x10 ^= u<<18 | u>>(32-18)
u = x15 + x11
x3 ^= u<<7 | u>>(32-7)
u = x3 + x15
x7 ^= u<<9 | u>>(32-9)
u = x7 + x3
x11 ^= u<<13 | u>>(32-13)
u = x11 + x7
x15 ^= u<<18 | u>>(32-18)
u = x0 + x3
x1 ^= u<<7 | u>>(32-7)
u = x1 + x0
x2 ^= u<<9 | u>>(32-9)
u = x2 + x1
x3 ^= u<<13 | u>>(32-13)
u = x3 + x2
x0 ^= u<<18 | u>>(32-18)
u = x5 + x4
x6 ^= u<<7 | u>>(32-7)
u = x6 + x5
x7 ^= u<<9 | u>>(32-9)
u = x7 + x6
x4 ^= u<<13 | u>>(32-13)
u = x4 + x7
x5 ^= u<<18 | u>>(32-18)
u = x10 + x9
x11 ^= u<<7 | u>>(32-7)
u = x11 + x10
x8 ^= u<<9 | u>>(32-9)
u = x8 + x11
x9 ^= u<<13 | u>>(32-13)
u = x9 + x8
x10 ^= u<<18 | u>>(32-18)
u = x15 + x14
x12 ^= u<<7 | u>>(32-7)
u = x12 + x15
x13 ^= u<<9 | u>>(32-9)
u = x13 + x12
x14 ^= u<<13 | u>>(32-13)
u = x14 + x13
x15 ^= u<<18 | u>>(32-18)
}
x0 += j0
x1 += j1
x2 += j2
x3 += j3
x4 += j4
x5 += j5
x6 += j6
x7 += j7
x8 += j8
x9 += j9
x10 += j10
x11 += j11
x12 += j12
x13 += j13
x14 += j14
x15 += j15
out[0] = byte(x0)
out[1] = byte(x0 >> 8)
out[2] = byte(x0 >> 16)
out[3] = byte(x0 >> 24)
out[4] = byte(x1)
out[5] = byte(x1 >> 8)
out[6] = byte(x1 >> 16)
out[7] = byte(x1 >> 24)
out[8] = byte(x2)
out[9] = byte(x2 >> 8)
out[10] = byte(x2 >> 16)
out[11] = byte(x2 >> 24)
out[12] = byte(x3)
out[13] = byte(x3 >> 8)
out[14] = byte(x3 >> 16)
out[15] = byte(x3 >> 24)
out[16] = byte(x4)
out[17] = byte(x4 >> 8)
out[18] = byte(x4 >> 16)
out[19] = byte(x4 >> 24)
out[20] = byte(x5)
out[21] = byte(x5 >> 8)
out[22] = byte(x5 >> 16)
out[23] = byte(x5 >> 24)
out[24] = byte(x6)
out[25] = byte(x6 >> 8)
out[26] = byte(x6 >> 16)
out[27] = byte(x6 >> 24)
out[28] = byte(x7)
out[29] = byte(x7 >> 8)
out[30] = byte(x7 >> 16)
out[31] = byte(x7 >> 24)
out[32] = byte(x8)
out[33] = byte(x8 >> 8)
out[34] = byte(x8 >> 16)
out[35] = byte(x8 >> 24)
out[36] = byte(x9)
out[37] = byte(x9 >> 8)
out[38] = byte(x9 >> 16)
out[39] = byte(x9 >> 24)
out[40] = byte(x10)
out[41] = byte(x10 >> 8)
out[42] = byte(x10 >> 16)
out[43] = byte(x10 >> 24)
out[44] = byte(x11)
out[45] = byte(x11 >> 8)
out[46] = byte(x11 >> 16)
out[47] = byte(x11 >> 24)
out[48] = byte(x12)
out[49] = byte(x12 >> 8)
out[50] = byte(x12 >> 16)
out[51] = byte(x12 >> 24)
out[52] = byte(x13)
out[53] = byte(x13 >> 8)
out[54] = byte(x13 >> 16)
out[55] = byte(x13 >> 24)
out[56] = byte(x14)
out[57] = byte(x14 >> 8)
out[58] = byte(x14 >> 16)
out[59] = byte(x14 >> 24)
out[60] = byte(x15)
out[61] = byte(x15 >> 8)
out[62] = byte(x15 >> 16)
out[63] = byte(x15 >> 24)
}
// XORKeyStream crypts bytes from in to out using the given key and counters.
// In and out must overlap entirely or not at all. Counter
// contains the raw salsa20 counter bytes (both nonce and block counter).
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
var block [64]byte
var counterCopy [16]byte
copy(counterCopy[:], counter[:])
for len(in) >= 64 {
core(&block, &counterCopy, key, &Sigma)
for i, x := range block {
out[i] = in[i] ^ x
}
u := uint32(1)
for i := 8; i < 16; i++ {
u += uint32(counterCopy[i])
counterCopy[i] = byte(u)
u >>= 8
}
in = in[64:]
out = out[64:]
}
if len(in) > 0 {
core(&block, &counterCopy, key, &Sigma)
for i, v := range in {
out[i] = v ^ block[i]
}
}
}

View file

@ -0,0 +1,54 @@
// Copyright 2012 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 salsa
import "testing"
func TestCore208(t *testing.T) {
in := [64]byte{
0x7e, 0x87, 0x9a, 0x21, 0x4f, 0x3e, 0xc9, 0x86,
0x7c, 0xa9, 0x40, 0xe6, 0x41, 0x71, 0x8f, 0x26,
0xba, 0xee, 0x55, 0x5b, 0x8c, 0x61, 0xc1, 0xb5,
0x0d, 0xf8, 0x46, 0x11, 0x6d, 0xcd, 0x3b, 0x1d,
0xee, 0x24, 0xf3, 0x19, 0xdf, 0x9b, 0x3d, 0x85,
0x14, 0x12, 0x1e, 0x4b, 0x5a, 0xc5, 0xaa, 0x32,
0x76, 0x02, 0x1d, 0x29, 0x09, 0xc7, 0x48, 0x29,
0xed, 0xeb, 0xc6, 0x8d, 0xb8, 0xb8, 0xc2, 0x5e}
out := [64]byte{
0xa4, 0x1f, 0x85, 0x9c, 0x66, 0x08, 0xcc, 0x99,
0x3b, 0x81, 0xca, 0xcb, 0x02, 0x0c, 0xef, 0x05,
0x04, 0x4b, 0x21, 0x81, 0xa2, 0xfd, 0x33, 0x7d,
0xfd, 0x7b, 0x1c, 0x63, 0x96, 0x68, 0x2f, 0x29,
0xb4, 0x39, 0x31, 0x68, 0xe3, 0xc9, 0xe6, 0xbc,
0xfe, 0x6b, 0xc5, 0xb7, 0xa0, 0x6d, 0x96, 0xba,
0xe4, 0x24, 0xcc, 0x10, 0x2c, 0x91, 0x74, 0x5c,
0x24, 0xad, 0x67, 0x3d, 0xc7, 0x61, 0x8f, 0x81,
}
Core208(&in, &in)
if in != out {
t.Errorf("expected %x, got %x", out, in)
}
}
func TestOutOfBoundsWrite(t *testing.T) {
// encrypted "0123456789"
cipherText := []byte{170, 166, 196, 104, 175, 121, 68, 44, 174, 51}
var counter [16]byte
var key [32]byte
want := "abcdefghij"
plainText := []byte(want)
defer func() {
err := recover()
if err == nil {
t.Error("XORKeyStream expected to panic on len(dst) < len(src), but didn't")
}
if plainText[3] == '3' {
t.Errorf("XORKeyStream did out of bounds write, want %v, got %v", want, string(plainText))
}
}()
XORKeyStream(plainText[:3], cipherText, &counter, &key)
}