initial release. tests pass at least.

This commit is contained in:
brent s. 2023-01-10 07:24:12 -05:00
parent 7b000530f3
commit 29dea9ae1f
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
19 changed files with 3443 additions and 27 deletions

2
.gitignore vendored
View File

@ -19,4 +19,4 @@
.idea/ .idea/


# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ !/vendor/

View File

@ -18,33 +18,42 @@ Last updated {localdatetime}


A Golang library variant of ChaCha20-Poly1305 that OpenSSH uses (`chacha20-poly1305@openssh.com`). A Golang library variant of ChaCha20-Poly1305 that OpenSSH uses (`chacha20-poly1305@openssh.com`).


Note that this module *only* supports the OpenSSH variant, and should only be used for key generation/parsing/modification/manipulation, not actual connection/stream encryption. WARNING: Note that this module *only* supports the OpenSSH variant, and should only be used for key generation/parsing/modification/manipulation, not actual connection/stream encryption.


== Why is this necessary? == Usage
The usage of this library is *very* limited in scope; it's only intended for low-level OpenSSH key operations.

Primarily, for use with https://git.r00t2.io/r00t2/go_sshkeys/src/branch/master[go_sshkeys^].

== FAQ

=== Why is this necessary?


Because Golang.org/x/crypto https://github.com/golang/go/issues/36646[removes functionality^] (even for https://github.com/golang/go/issues/44226[very common tech^]) and thinks OpenSSH is a "weird" use case. That's a direct reference; they called it "weird". Because Golang.org/x/crypto https://github.com/golang/go/issues/36646[removes functionality^] (even for https://github.com/golang/go/issues/44226[very common tech^]) and thinks OpenSSH is a "weird" use case. That's a direct reference; they called it "weird".


I *really, really* hope this library is https://github.com/golang/go/issues/57699[no longer necessary^] by the time I'm done writing it but based on my past experiences with core Golang devs, my expectations are extremely low. I *really, really* hope this library is https://github.com/golang/go/issues/57699[no longer necessary^] by the time I'm done writing it but based on my past experiences with core Golang devs, my expectations are extremely low.


_Narrator: It was still necessary ._

They have no decent support for OpenSSH keys or lower-level operations. And guess what -- sometimes you *need* lower-level functionality. Who knew? They have no decent support for OpenSSH keys or lower-level operations. And guess what -- sometimes you *need* lower-level functionality. Who knew?


So now because I'm just a single individual, bug fixes will probably lag behind upstream. All because Golang devs decided the OpenSSH variant was "too weird". So now because I'm just a single individual, bug fixes will probably lag behind upstream if I have to re-vendor because I'm not maintaining an entire fork of golang.org/x/crypto. All because Golang devs decided the OpenSSH variant was "too weird".


But, of course, not "weird" enough to https://github.com/golang/crypto/blob/3d872d042823aed41f28af3b13beb27c0c9b1e35/ssh/cipher.go#L652[not support the *wire* protocol^] for SSH. Just the key encryption. Because of course. And not publicly exposed either. Because *of course*. But, of course, not "weird" enough to https://github.com/golang/crypto/blob/3d872d042823aed41f28af3b13beb27c0c9b1e35/ssh/cipher.go#L652[not support the *wire* protocol^] for SSH. Just the key encryption. Because of course. And not publicly exposed either. Because *of course*.


Assholes. Assholes.


== Why is the name so ugly? === Why is the name so ugly?


I couldn't think of a better one and I wanted something notably distinct from the stdlib-x naming. I couldn't think of a better one and I wanted something notably distinct from the stdlib-x naming.


And module names can't include the `@` symbol. And module names can't include the `@` symbol.


== Why don't you expose the rest of ChaCha20/Poly1305/ChaCha20-Poly1305? === Why don't you expose the rest of low-level ChaCha20/Poly1305/ChaCha20-Poly1305?


* To keep code changes from upstream light (and thus easier to debug, audit, etc.) * To keep code changes from upstream light (and thus easier to debug, audit, etc.)
* Because otherwise the module name is inaccurate * Because otherwise the module name is inaccurate
** Because OpenSSH has their own specific variant ** Because OpenSSH has their own specific variant
** Which means we can handle SSH-specific functionality if needed ** Which means we can handle SSH-specific functionality if needed
* Because golang/x/crypto has made it painfully clear that if you want something that deviates from what they *think* is "best practice", you need to do it yourself * Because golang.org/x/crypto has made it painfully clear that if you want something that deviates from what they *think* is "best practice", you need to do it yourself
** Which ironically is something they also brand an "anti-pattern" which is just \*chef's kiss* ** Which ironically is something they also brand an "anti-pattern" which is just \*chef's kiss*

View File

@ -0,0 +1,13 @@
--- a/vendor/golang.org/x/crypto/chacha20/chacha_generic.go 2023-01-09 05:16:30.912219212 -0500
+++ b/vendor/golang.org/x/crypto/chacha20/chacha_generic.go 2023-01-10 02:20:55.150612278 -0500
@@ -24,7 +24,9 @@
//
// Note that this is too short to be safely generated at random if the same
// key is reused more than 2³² times.
- NonceSize = 12
+ //NonceSize = 12
+ // BITE ME, GOLANG DEVS.
+ NonceSize = 16

// NonceSizeX is the size of the nonce used with the XChaCha20 variant of
// this cipher, in bytes.

View File

@ -0,0 +1,13 @@
--- a/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.go 2023-01-09 05:16:30.912219212 -0500
+++ b/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.go 2023-01-10 02:58:31.006536303 -0500
@@ -21,7 +21,9 @@
//
// Note that this is too short to be safely generated at random if the same
// key is reused more than 2³² times.
- NonceSize = 12
+ // NonceSize = 12
+ // BITE ME, GOLANG DEVS.
+ NonceSize = 16

// NonceSizeX is the size of the nonce used with the XChaCha20-Poly1305
// variant of this AEAD, in bytes.

34
algos_test.go Normal file
View File

@ -0,0 +1,34 @@
package cc20p1305ssh

import (
`testing`

`golang.org/x/crypto/chacha20`
`golang.org/x/crypto/chacha20poly1305`
)

func TestChaCha20FixedNonceSize(t *testing.T) {

if NonceSize != chacha20.NonceSize {
t.Fatal(
"Mismatched ChaCha20 nonce sizes;\n" +
"\tgolang.org/x/crypto/chacha20 only supports 12 or 24 bytes nonce size,\n\t" +
"the chacha20poly1305@openssh.com variant only supports a nonce of 16 bytes.\n\t" +
"vendor/golang.org/x/crypto/chacha20/chacha_generic.go:NonceSize must be modified.",
)
}
t.Log("The nonce size modification for chacha20 is correct.")
}

func TestChaCha20Poly1305FixedNonceSize(t *testing.T) {

if NonceSize != chacha20poly1305.NonceSize {
t.Fatal(
"Mismatched ChaCha20Poly1305 nonce sizes;\n" +
"\tgolang.org/x/crypto/chacha20poly1305 only supports 12 or 24 bytes nonce size,\n\t" +
"the chacha20poly1305@openssh.com variant only supports a nonce of 16 bytes.\n\t" +
"vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.go:NonceSize must be modified.",
)
}
t.Log("The nonce size modification for chacha20poly1305 is correct.")
}

View File

@ -35,14 +35,27 @@ const (
*/ */
NonceSize int = 16 NonceSize int = 16


// TagLen is the length of the Poly1305 tag. // PolyKeySize is the amount of the cipher result of chacha20.
TagLen int = poly1305.TagSize PolyKeySize int = 32


// DefaultRounds specifies the number of default rounds to use if using the provided KDF derivation and the specified rounds are 0 or negative. // TagSize is the length of the Poly1305 tag.
DefaultRounds int = 16 TagSize int = poly1305.TagSize
) )


var ( var (
// initBlock is used at counter 0 in chacha20 to get the poly1305 key.
initBlock []byte = []byte{
// 64 bytes
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,
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,
}

// iv is the constant fixed IV. // iv is the constant fixed IV.
iv []byte = []byte{ iv []byte = []byte{
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,

View File

@ -1,20 +1,79 @@
package cc20p1305ssh package cc20p1305ssh


import ( var (
`testing` /*
testKdfKey is the key that would be returned by bcrypt_pbkdf.


`golang.org/x/crypto/chacha20` It would be created using the passphrase (seed) "test", with 16 rounds,
) and 64 bytes keysize.

*/
func TestFixedNonceSize(t *testing.T) { testKdfKey []byte = []byte{

// 64 bytes
if NonceSize != chacha20.NonceSize { 0xd7, 0xe4, 0x7e, 0xf3, 0x7c, 0xfd, 0x80, 0x6d,
t.Error( 0x48, 0xfd, 0x8b, 0x46, 0xec, 0x53, 0x51, 0xef,
"Mismatched ChaCha20 nonce sizes;\n" + 0x3f, 0x15, 0xf8, 0xb0, 0xa2, 0x30, 0xcf, 0x60,
"\tgolang.org/x/crypto/chacha20 only supports 12 or 24 bytes nonce size,\n\t" + 0x2c, 0x09, 0xdd, 0x44, 0x91, 0xe9, 0x54, 0xc8,
"the chacha20poly1305@openssh.com variant only supports a nonce of 16 bytes.", 0x72, 0x03, 0x15, 0xe4, 0x83, 0x54, 0x30, 0xca,
) 0x8a, 0xe1, 0x9b, 0x15, 0x3f, 0xbc, 0xbe, 0x45,
t.Fail() 0x17, 0xc5, 0x03, 0xe0, 0xc9, 0xda, 0x77, 0xff,
0xe5, 0x95, 0xa3, 0x33, 0x17, 0xad, 0x4d, 0xe7,
} }
t.Log("The nonce size modification is correct.")
} // testPoly1305Tag is the poly1305 tag.
testPoly1305Tag []byte = []byte{
// 16 bytes
0x7d, 0x98, 0x49, 0xd7,
0x60, 0x2b, 0x49, 0xf9,
0xcf, 0xa0, 0x2d, 0x9f,
0x39, 0x66, 0x2c, 0x66,
}

// testEncBlock is the encrypted private key of an example ssh-ed25519 private key, encrypted with the passphrase "test" (block "4.0.1" in go_sshkeys).
// We skip the KDF for tests, though (see testKdfKey).
testEncBlock []byte = []byte{
// 152 bytes
0x9b, 0x24, 0x63, 0x64, 0xe7, 0xbe, 0xf7, 0xc1,
0xc7, 0x20, 0xe1, 0x0e, 0xce, 0x79, 0x3b, 0x7c,
0xe0, 0x61, 0xa4, 0xa8, 0xf3, 0x2c, 0x18, 0x36,
0x3b, 0xb3, 0x85, 0x82, 0xfd, 0xed, 0x5d, 0xb6,
0xd3, 0xb5, 0x93, 0x16, 0x3c, 0x5d, 0x06, 0xaa,
0xb4, 0x9f, 0xc4, 0x6a, 0x87, 0xa1, 0xcb, 0x29,
0xa0, 0x8f, 0x53, 0x99, 0x2b, 0x86, 0x56, 0xc5,
0xf5, 0xaf, 0xeb, 0x1b, 0x86, 0x5c, 0x0e, 0x21,
0x52, 0x4f, 0xf4, 0x76, 0xb8, 0x1d, 0x8a, 0x8b,
0xaa, 0xda, 0x2a, 0xed, 0x08, 0xc1, 0xcb, 0x21,
0x8a, 0x6a, 0x2e, 0xa5, 0x2d, 0x9d, 0x01, 0xe7,
0x4c, 0x7b, 0x2e, 0x42, 0x30, 0x51, 0x64, 0x5f,
0x9f, 0x46, 0xa6, 0x2b, 0x15, 0x73, 0xd2, 0xfa,
0xa4, 0xf4, 0xdc, 0xe9, 0xfd, 0xf8, 0x91, 0xa3,
0x11, 0xb5, 0xce, 0x18, 0xd7, 0x4a, 0xff, 0xa9,
0xea, 0xd3, 0x7d, 0x44, 0xd0, 0xba, 0xec, 0x16,
0xc3, 0xc6, 0xcd, 0x1d, 0xa2, 0x2d, 0x25, 0x17,
0x45, 0x98, 0x9b, 0x7d, 0x06, 0xf2, 0x77, 0x17,
0x54, 0xd2, 0xee, 0xc6, 0xa6, 0x64, 0xbc, 0x9a,
}

// testPlainBlock should match the decrypted block of testEncBlock.
testPlainBlock []byte = []byte{
// 152 bytes
0x09, 0xf4, 0xb5, 0x8f, 0x09, 0xf4, 0xb5, 0x8f,
0x00, 0x00, 0x00, 0x0b, 0x73, 0x73, 0x68, 0x2d,
0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x00,
0x00, 0x00, 0x20, 0xb8, 0xd3, 0x93, 0x30, 0x65,
0x72, 0xa6, 0xe5, 0x7e, 0x22, 0xa1, 0x64, 0x5b,
0x97, 0x9b, 0x01, 0x60, 0x7b, 0xdb, 0x1d, 0xcd,
0xcf, 0x92, 0x01, 0xd4, 0x80, 0xc3, 0xa9, 0x0a,
0xbd, 0xad, 0x20, 0x00, 0x00, 0x00, 0x40, 0x2d,
0xad, 0x50, 0x3f, 0x38, 0x88, 0xe2, 0xc1, 0x57,
0x0b, 0x1c, 0xa4, 0xcf, 0xbc, 0x7c, 0x39, 0xac,
0x19, 0xf2, 0x7b, 0x02, 0xac, 0x91, 0x1b, 0xd6,
0x05, 0x06, 0x00, 0xff, 0xe9, 0xb6, 0x83, 0xb8,
0xd3, 0x93, 0x30, 0x65, 0x72, 0xa6, 0xe5, 0x7e,
0x22, 0xa1, 0x64, 0x5b, 0x97, 0x9b, 0x01, 0x60,
0x7b, 0xdb, 0x1d, 0xcd, 0xcf, 0x92, 0x01, 0xd4,
0x80, 0xc3, 0xa9, 0x0a, 0xbd, 0xad, 0x20, 0x00,
0x00, 0x00, 0x14, 0x62, 0x74, 0x73, 0x40, 0x64,
0x61, 0x77, 0x69, 0x64, 0x2e, 0x72, 0x30, 0x30,
0x74, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x01,
}
)

11
errs.go Normal file
View File

@ -0,0 +1,11 @@
package cc20p1305ssh

import (
`errors`
)

var (
ErrInvalidKeySize error = errors.New("a key of invalid size was provided; it must be at least 32 bytes")
ErrInvalidTag = errors.New("the provided tag does not match the ciphertext")
ErrInvalidTagSize = errors.New("a tag of invalid size was provided; it must be at least 16 bytes")
)

33
funcs.go Normal file
View File

@ -0,0 +1,33 @@
package cc20p1305ssh

/*
New returns a cipher.AEAD from KDF-derived key.

Currently, key should be KDFKeySize bytes and returned by bcrypt_pbkdf as it's currently the
only OpenSSH-supported KDF. It is up to the caller to perform the appropriate KDF.

Per the chacha20polycom1305@openssh.com specification, only the first KeySize bytes of key
is used for encrypting the private key. The second half (the canonical key is 64 bytes)
would be used for traffic purposes, but since this is a static blob it is not used.

If key is nil or <KDFKeySize bytes in length, an error ErrInvalidKeySize will be returned.

*DO NOT USE crypter FOR STREAMS. THIS SHOULD ONLY BE USED TO ENCRYPT AN OPENSSH PRIVATE KEY.*
*/
func New(key []byte) (crypter *ChaCha20Poly1305OpenSSH, err error) {

var crypterReal ChaCha20Poly1305OpenSSH

if key == nil || len(key) < KDFKeySize {
err = ErrInvalidKeySize
return
}

crypterReal = ChaCha20Poly1305OpenSSH{}
copy(crypterReal.kdfKey[:], key[:KDFKeySize])
copy(crypterReal.realKey[:], key[:KeySize])

crypter = &crypterReal

return
}

View File

@ -0,0 +1,100 @@
package cc20p1305ssh

import (
`golang.org/x/crypto/chacha20`
`golang.org/x/crypto/poly1305`
)

/*
Decrypt decrypts and authenticates ciphertext returning the decrypted format of ciphertext.

If tag is nil or empty, it will be assumed that the tag is appended to the end of ciphertext.

If tag is specified but is <TagSize, an error ErrInvalidTagSize will be returned.
*/
func (c *ChaCha20Poly1305OpenSSH) Decrypt(ciphertext, tag []byte) (decrypted []byte, err error) {

var tagIdx int
var firstBlock []byte
var polyKey [PolyKeySize]byte
var realTag [TagSize]byte
var cc20 *chacha20.Cipher

if tag == nil || len(tag) == 0 {
tagIdx = len(ciphertext) - TagSize
tag = ciphertext[tagIdx:]
ciphertext = ciphertext[:tagIdx]
}
if tag == nil || len(tag) != TagSize {
err = ErrInvalidTagSize
return
}
copy(realTag[:], tag)

// We need the crypter.
if cc20, err = chacha20.NewUnauthenticatedCipher(c.realKey[:], iv); err != nil {
return
}

// First we need the poly1305 key. This also sets the counter to 1.
firstBlock = make([]byte, len(initBlock))
cc20.XORKeyStream(firstBlock, initBlock)
copy(polyKey[:], firstBlock[:PolyKeySize])
// We explicitly set the counter to 1, just in case.
cc20.SetCounter(1)

// And verify against the tag.
if !poly1305.Verify(&realTag, ciphertext, &polyKey) {
err = ErrInvalidTag
return
}

// Finally, decrypt.
decrypted = make([]byte, len(ciphertext))
cc20.XORKeyStream(decrypted, ciphertext)

return
}

/*
Encrypt encrypts and authenticates plaintext returning the encrypted format of plaintext.

If polyTag is nil or <TagSize bytes in length, an error ErrInvalidTagSize will be returned.
*/
func (c *ChaCha20Poly1305OpenSSH) Encrypt(plaintext []byte) (encrypted, tag []byte, err error) {

var polyKey [PolyKeySize]byte
var firstBlock []byte
var cc20 *chacha20.Cipher
var poly *poly1305.MAC
var tagTmp []byte

// TODO: check size of plaintext?
/*
if uint64(len(plaintext)) > (1<<38)-64 {
panic("chacha20poly1305: plaintext too large")
}
*/

// We need the crypter.
if cc20, err = chacha20.NewUnauthenticatedCipher(c.realKey[:], iv); err != nil {
return
}

// First we need the poly1305 key. This also sets the counter to 1.
firstBlock = make([]byte, len(initBlock))
cc20.XORKeyStream(firstBlock, initBlock)
copy(polyKey[:], firstBlock[:PolyKeySize])
// We explicitly set the counter to 1, just in case.
cc20.SetCounter(1)
encrypted = make([]byte, len(plaintext))
cc20.XORKeyStream(encrypted, plaintext)

poly = poly1305.New(&polyKey)
poly.Write(encrypted)
tagTmp = poly.Sum(nil)
tag = make([]byte, TagSize)
copy(tag, tagTmp[:TagSize])

return
}

53
funcs_test.go Normal file
View File

@ -0,0 +1,53 @@
package cc20p1305ssh

import (
`bytes`
`encoding/hex`
`testing`
)

func TestNewCipher(t *testing.T) {

var err error
var c *ChaCha20Poly1305OpenSSH
var enc []byte
var plain []byte
var tag []byte

if c, err = New(testKdfKey); err != nil {
t.Fatal(err)
}

t.Logf("ChaCha20Poly1305OpenSSH:\n%#v", c)

// Decrypt...
if plain, err = c.Decrypt(testEncBlock, testPoly1305Tag); err != nil {
t.Log("Failed during decrypt!")
t.Fatal(err)
}
t.Logf("plain:\n%v", hex.EncodeToString(plain))

if !bytes.Equal(plain, testPlainBlock) {
t.Fatal("decrypted does not match!")
}

// And encrypt...
if enc, tag, err = c.Encrypt(plain); err != nil {
t.Log("Failed during encrypt!")
t.Fatal(err)
}
t.Logf("crypted:\n%v", hex.EncodeToString(enc))

if !bytes.Equal(enc, testEncBlock) {
t.Fatal("encrypted does not match!")
}

// And check tags.
if !bytes.Equal(tag, testPoly1305Tag) {
t.Logf("real tag: %v", hex.EncodeToString(testPoly1305Tag))
t.Logf("generated tag: %v", hex.EncodeToString(tag))
t.Fatal("tags do not match!")
}

t.Log("The cipher is functioning correctly.")
}

11
types.go Normal file
View File

@ -0,0 +1,11 @@
package cc20p1305ssh

/*
ChaCha20Poly1305OpenSSH is an implementation of the chacha20poly1305@openssh.com private key cipher.

Use New to return a usable version.
*/
type ChaCha20Poly1305OpenSSH struct {
kdfKey [KDFKeySize]byte
realKey [KeySize]byte
}

View File

@ -0,0 +1,100 @@
// 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 chacha20poly1305 implements the ChaCha20-Poly1305 AEAD and its
// extended nonce variant XChaCha20-Poly1305, as specified in RFC 8439 and
// draft-irtf-cfrg-xchacha-01.
package chacha20poly1305 // import "golang.org/x/crypto/chacha20poly1305"

import (
"crypto/cipher"
"errors"
)

const (
// KeySize is the size of the key used by this AEAD, in bytes.
KeySize = 32

// NonceSize is the size of the nonce used with the standard variant of this
// AEAD, in bytes.
//
// Note that this is too short to be safely generated at random if the same
// key is reused more than 2³² times.
// NonceSize = 12
// BITE ME, GOLANG DEVS.
NonceSize = 16

// NonceSizeX is the size of the nonce used with the XChaCha20-Poly1305
// variant of this AEAD, in bytes.
NonceSizeX = 24

// Overhead is the size of the Poly1305 authentication tag, and the
// difference between a ciphertext length and its plaintext.
Overhead = 16
)

type chacha20poly1305 struct {
key [KeySize]byte
}

// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
func New(key []byte) (cipher.AEAD, error) {
if len(key) != KeySize {
return nil, errors.New("chacha20poly1305: bad key length")
}
ret := new(chacha20poly1305)
copy(ret.key[:], key)
return ret, nil
}

func (c *chacha20poly1305) NonceSize() int {
return NonceSize
}

func (c *chacha20poly1305) Overhead() int {
return Overhead
}

func (c *chacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != NonceSize {
panic("chacha20poly1305: bad nonce length passed to Seal")
}

if uint64(len(plaintext)) > (1<<38)-64 {
panic("chacha20poly1305: plaintext too large")
}

return c.seal(dst, nonce, plaintext, additionalData)
}

var errOpen = errors.New("chacha20poly1305: message authentication failed")

func (c *chacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != NonceSize {
panic("chacha20poly1305: bad nonce length passed to Open")
}
if len(ciphertext) < 16 {
return nil, errOpen
}
if uint64(len(ciphertext)) > (1<<38)-48 {
panic("chacha20poly1305: ciphertext too large")
}

return c.open(dst, nonce, ciphertext, additionalData)
}

// 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
}

View File

@ -0,0 +1,87 @@
// 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.

//go:build gc && !purego
// +build gc,!purego

package chacha20poly1305

import (
"encoding/binary"

"golang.org/x/crypto/internal/alias"
"golang.org/x/sys/cpu"
)

//go:noescape
func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool

//go:noescape
func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)

var (
useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2
)

// setupState writes a ChaCha20 input matrix to state. See
// https://tools.ietf.org/html/rfc7539#section-2.3.
func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
state[0] = 0x61707865
state[1] = 0x3320646e
state[2] = 0x79622d32
state[3] = 0x6b206574

state[4] = binary.LittleEndian.Uint32(key[0:4])
state[5] = binary.LittleEndian.Uint32(key[4:8])
state[6] = binary.LittleEndian.Uint32(key[8:12])
state[7] = binary.LittleEndian.Uint32(key[12:16])
state[8] = binary.LittleEndian.Uint32(key[16:20])
state[9] = binary.LittleEndian.Uint32(key[20:24])
state[10] = binary.LittleEndian.Uint32(key[24:28])
state[11] = binary.LittleEndian.Uint32(key[28:32])

state[12] = 0
state[13] = binary.LittleEndian.Uint32(nonce[0:4])
state[14] = binary.LittleEndian.Uint32(nonce[4:8])
state[15] = binary.LittleEndian.Uint32(nonce[8:12])
}

func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
if !cpu.X86.HasSSSE3 {
return c.sealGeneric(dst, nonce, plaintext, additionalData)
}

var state [16]uint32
setupState(&state, &c.key, nonce)

ret, out := sliceForAppend(dst, len(plaintext)+16)
if alias.InexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap")
}
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
return ret
}

func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if !cpu.X86.HasSSSE3 {
return c.openGeneric(dst, nonce, ciphertext, additionalData)
}

var state [16]uint32
setupState(&state, &c.key, nonce)

ciphertext = ciphertext[:len(ciphertext)-16]
ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap")
}
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
for i := range out {
out[i] = 0
}
return nil, errOpen
}

return ret, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
// 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 chacha20poly1305

import (
"encoding/binary"

"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/internal/alias"
"golang.org/x/crypto/internal/poly1305"
)

func writeWithPadding(p *poly1305.MAC, b []byte) {
p.Write(b)
if rem := len(b) % 16; rem != 0 {
var buf [16]byte
padLen := 16 - rem
p.Write(buf[:padLen])
}
}

func writeUint64(p *poly1305.MAC, n int) {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], uint64(n))
p.Write(buf[:])
}

func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []byte) []byte {
ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
if alias.InexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap")
}

var polyKey [32]byte
s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
s.XORKeyStream(polyKey[:], polyKey[:])
s.SetCounter(1) // set the counter to 1, skipping 32 bytes
s.XORKeyStream(ciphertext, plaintext)

p := poly1305.New(&polyKey)
writeWithPadding(p, additionalData)
writeWithPadding(p, ciphertext)
writeUint64(p, len(additionalData))
writeUint64(p, len(plaintext))
p.Sum(tag[:0])

return ret
}

func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
tag := ciphertext[len(ciphertext)-16:]
ciphertext = ciphertext[:len(ciphertext)-16]

var polyKey [32]byte
s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
s.XORKeyStream(polyKey[:], polyKey[:])
s.SetCounter(1) // set the counter to 1, skipping 32 bytes

p := poly1305.New(&polyKey)
writeWithPadding(p, additionalData)
writeWithPadding(p, ciphertext)
writeUint64(p, len(additionalData))
writeUint64(p, len(ciphertext))

ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap")
}
if !p.Verify(tag) {
for i := range out {
out[i] = 0
}
return nil, errOpen
}

s.XORKeyStream(out, ciphertext)
return ret, nil
}

View File

@ -0,0 +1,16 @@
// 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.

//go:build !amd64 || !gc || purego
// +build !amd64 !gc purego

package chacha20poly1305

func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
return c.sealGeneric(dst, nonce, plaintext, additionalData)
}

func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
return c.openGeneric(dst, nonce, ciphertext, additionalData)
}

View File

@ -0,0 +1,86 @@
// Copyright 2018 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 chacha20poly1305

import (
"crypto/cipher"
"errors"

"golang.org/x/crypto/chacha20"
)

type xchacha20poly1305 struct {
key [KeySize]byte
}

// NewX returns a XChaCha20-Poly1305 AEAD that uses the given 256-bit key.
//
// XChaCha20-Poly1305 is a ChaCha20-Poly1305 variant that takes a longer nonce,
// suitable to be generated randomly without risk of collisions. It should be
// preferred when nonce uniqueness cannot be trivially ensured, or whenever
// nonces are randomly generated.
func NewX(key []byte) (cipher.AEAD, error) {
if len(key) != KeySize {
return nil, errors.New("chacha20poly1305: bad key length")
}
ret := new(xchacha20poly1305)
copy(ret.key[:], key)
return ret, nil
}

func (*xchacha20poly1305) NonceSize() int {
return NonceSizeX
}

func (*xchacha20poly1305) Overhead() int {
return Overhead
}

func (x *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != NonceSizeX {
panic("chacha20poly1305: bad nonce length passed to Seal")
}

// XChaCha20-Poly1305 technically supports a 64-bit counter, so there is no
// size limit. However, since we reuse the ChaCha20-Poly1305 implementation,
// the second half of the counter is not available. This is unlikely to be
// an issue because the cipher.AEAD API requires the entire message to be in
// memory, and the counter overflows at 256 GB.
if uint64(len(plaintext)) > (1<<38)-64 {
panic("chacha20poly1305: plaintext too large")
}

c := new(chacha20poly1305)
hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16])
copy(c.key[:], hKey)

// The first 4 bytes of the final nonce are unused counter space.
cNonce := make([]byte, NonceSize)
copy(cNonce[4:12], nonce[16:24])

return c.seal(dst, cNonce[:], plaintext, additionalData)
}

func (x *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != NonceSizeX {
panic("chacha20poly1305: bad nonce length passed to Open")
}
if len(ciphertext) < 16 {
return nil, errOpen
}
if uint64(len(ciphertext)) > (1<<38)-48 {
panic("chacha20poly1305: ciphertext too large")
}

c := new(chacha20poly1305)
hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16])
copy(c.key[:], hKey)

// The first 4 bytes of the final nonce are unused counter space.
cNonce := make([]byte, NonceSize)
copy(cNonce[4:12], nonce[16:24])

return c.open(dst, cNonce[:], ciphertext, additionalData)
}

1
vendor/modules.txt vendored
View File

@ -1,6 +1,7 @@
# golang.org/x/crypto v0.5.0 # golang.org/x/crypto v0.5.0
## explicit; go 1.17 ## explicit; go 1.17
golang.org/x/crypto/chacha20 golang.org/x/crypto/chacha20
golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/internal/alias golang.org/x/crypto/internal/alias
golang.org/x/crypto/internal/poly1305 golang.org/x/crypto/internal/poly1305
golang.org/x/crypto/poly1305 golang.org/x/crypto/poly1305