Move to upstream go-xmpp.
parent
557d105238
commit
0805b1f06a
@ -1,3 +0,0 @@
|
|||||||
# This source code refers to The Go Authors for copyright purposes.
|
|
||||||
# The master list of authors is in the main Go distribution,
|
|
||||||
# visible at https://tip.golang.org/AUTHORS.
|
|
@ -1,3 +0,0 @@
|
|||||||
# This source code was written by the Go contributors.
|
|
||||||
# The master list of contributors is in the main Go distribution,
|
|
||||||
# visible at https://tip.golang.org/CONTRIBUTORS.
|
|
@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2009 The Go Authors. 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 Google Inc. 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.
|
|
@ -1,22 +0,0 @@
|
|||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
||||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
||||||
patent license to make, have made, use, offer to sell, sell, import,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
@ -1,381 +0,0 @@
|
|||||||
package bitcurves
|
|
||||||
|
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Copyright 2011 ThePiachu. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package bitelliptic implements several Koblitz elliptic curves over prime
|
|
||||||
// fields.
|
|
||||||
|
|
||||||
// This package operates, internally, on Jacobian coordinates. For a given
|
|
||||||
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
|
|
||||||
// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole
|
|
||||||
// calculation can be performed within the transform (as in ScalarMult and
|
|
||||||
// ScalarBaseMult). But even for Add and Double, it's faster to apply and
|
|
||||||
// reverse the transform than to operate in affine coordinates.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/elliptic"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A BitCurve represents a Koblitz Curve with a=0.
|
|
||||||
// See http://www.hyperelliptic.org/EFD/g1p/auto-shortw.html
|
|
||||||
type BitCurve struct {
|
|
||||||
Name string
|
|
||||||
P *big.Int // the order of the underlying field
|
|
||||||
N *big.Int // the order of the base point
|
|
||||||
B *big.Int // the constant of the BitCurve equation
|
|
||||||
Gx, Gy *big.Int // (x,y) of the base point
|
|
||||||
BitSize int // the size of the underlying field
|
|
||||||
}
|
|
||||||
|
|
||||||
// Params returns the parameters of the given BitCurve (see BitCurve struct)
|
|
||||||
func (bitCurve *BitCurve) Params() (cp *elliptic.CurveParams) {
|
|
||||||
cp = new(elliptic.CurveParams)
|
|
||||||
cp.Name = bitCurve.Name
|
|
||||||
cp.P = bitCurve.P
|
|
||||||
cp.N = bitCurve.N
|
|
||||||
cp.Gx = bitCurve.Gx
|
|
||||||
cp.Gy = bitCurve.Gy
|
|
||||||
cp.BitSize = bitCurve.BitSize
|
|
||||||
return cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsOnCurve returns true if the given (x,y) lies on the BitCurve.
|
|
||||||
func (bitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
|
|
||||||
// y² = x³ + b
|
|
||||||
y2 := new(big.Int).Mul(y, y) //y²
|
|
||||||
y2.Mod(y2, bitCurve.P) //y²%P
|
|
||||||
|
|
||||||
x3 := new(big.Int).Mul(x, x) //x²
|
|
||||||
x3.Mul(x3, x) //x³
|
|
||||||
|
|
||||||
x3.Add(x3, bitCurve.B) //x³+B
|
|
||||||
x3.Mod(x3, bitCurve.P) //(x³+B)%P
|
|
||||||
|
|
||||||
return x3.Cmp(y2) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// affineFromJacobian reverses the Jacobian transform. See the comment at the
|
|
||||||
// top of the file.
|
|
||||||
func (bitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
|
|
||||||
if z.Cmp(big.NewInt(0)) == 0 {
|
|
||||||
panic("bitcurve: Can't convert to affine with Jacobian Z = 0")
|
|
||||||
}
|
|
||||||
// x = YZ^2 mod P
|
|
||||||
zinv := new(big.Int).ModInverse(z, bitCurve.P)
|
|
||||||
zinvsq := new(big.Int).Mul(zinv, zinv)
|
|
||||||
|
|
||||||
xOut = new(big.Int).Mul(x, zinvsq)
|
|
||||||
xOut.Mod(xOut, bitCurve.P)
|
|
||||||
// y = YZ^3 mod P
|
|
||||||
zinvsq.Mul(zinvsq, zinv)
|
|
||||||
yOut = new(big.Int).Mul(y, zinvsq)
|
|
||||||
yOut.Mod(yOut, bitCurve.P)
|
|
||||||
return xOut, yOut
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add returns the sum of (x1,y1) and (x2,y2)
|
|
||||||
func (bitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
|
|
||||||
z := new(big.Int).SetInt64(1)
|
|
||||||
x, y, z := bitCurve.addJacobian(x1, y1, z, x2, y2, z)
|
|
||||||
return bitCurve.affineFromJacobian(x, y, z)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
|
|
||||||
// (x2, y2, z2) and returns their sum, also in Jacobian form.
|
|
||||||
func (bitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
|
|
||||||
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
|
|
||||||
z1z1 := new(big.Int).Mul(z1, z1)
|
|
||||||
z1z1.Mod(z1z1, bitCurve.P)
|
|
||||||
z2z2 := new(big.Int).Mul(z2, z2)
|
|
||||||
z2z2.Mod(z2z2, bitCurve.P)
|
|
||||||
|
|
||||||
u1 := new(big.Int).Mul(x1, z2z2)
|
|
||||||
u1.Mod(u1, bitCurve.P)
|
|
||||||
u2 := new(big.Int).Mul(x2, z1z1)
|
|
||||||
u2.Mod(u2, bitCurve.P)
|
|
||||||
h := new(big.Int).Sub(u2, u1)
|
|
||||||
if h.Sign() == -1 {
|
|
||||||
h.Add(h, bitCurve.P)
|
|
||||||
}
|
|
||||||
i := new(big.Int).Lsh(h, 1)
|
|
||||||
i.Mul(i, i)
|
|
||||||
j := new(big.Int).Mul(h, i)
|
|
||||||
|
|
||||||
s1 := new(big.Int).Mul(y1, z2)
|
|
||||||
s1.Mul(s1, z2z2)
|
|
||||||
s1.Mod(s1, bitCurve.P)
|
|
||||||
s2 := new(big.Int).Mul(y2, z1)
|
|
||||||
s2.Mul(s2, z1z1)
|
|
||||||
s2.Mod(s2, bitCurve.P)
|
|
||||||
r := new(big.Int).Sub(s2, s1)
|
|
||||||
if r.Sign() == -1 {
|
|
||||||
r.Add(r, bitCurve.P)
|
|
||||||
}
|
|
||||||
r.Lsh(r, 1)
|
|
||||||
v := new(big.Int).Mul(u1, i)
|
|
||||||
|
|
||||||
x3 := new(big.Int).Set(r)
|
|
||||||
x3.Mul(x3, x3)
|
|
||||||
x3.Sub(x3, j)
|
|
||||||
x3.Sub(x3, v)
|
|
||||||
x3.Sub(x3, v)
|
|
||||||
x3.Mod(x3, bitCurve.P)
|
|
||||||
|
|
||||||
y3 := new(big.Int).Set(r)
|
|
||||||
v.Sub(v, x3)
|
|
||||||
y3.Mul(y3, v)
|
|
||||||
s1.Mul(s1, j)
|
|
||||||
s1.Lsh(s1, 1)
|
|
||||||
y3.Sub(y3, s1)
|
|
||||||
y3.Mod(y3, bitCurve.P)
|
|
||||||
|
|
||||||
z3 := new(big.Int).Add(z1, z2)
|
|
||||||
z3.Mul(z3, z3)
|
|
||||||
z3.Sub(z3, z1z1)
|
|
||||||
if z3.Sign() == -1 {
|
|
||||||
z3.Add(z3, bitCurve.P)
|
|
||||||
}
|
|
||||||
z3.Sub(z3, z2z2)
|
|
||||||
if z3.Sign() == -1 {
|
|
||||||
z3.Add(z3, bitCurve.P)
|
|
||||||
}
|
|
||||||
z3.Mul(z3, h)
|
|
||||||
z3.Mod(z3, bitCurve.P)
|
|
||||||
|
|
||||||
return x3, y3, z3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double returns 2*(x,y)
|
|
||||||
func (bitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
|
|
||||||
z1 := new(big.Int).SetInt64(1)
|
|
||||||
return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
|
|
||||||
// returns its double, also in Jacobian form.
|
|
||||||
func (bitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
|
|
||||||
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
|
|
||||||
|
|
||||||
a := new(big.Int).Mul(x, x) //X1²
|
|
||||||
b := new(big.Int).Mul(y, y) //Y1²
|
|
||||||
c := new(big.Int).Mul(b, b) //B²
|
|
||||||
|
|
||||||
d := new(big.Int).Add(x, b) //X1+B
|
|
||||||
d.Mul(d, d) //(X1+B)²
|
|
||||||
d.Sub(d, a) //(X1+B)²-A
|
|
||||||
d.Sub(d, c) //(X1+B)²-A-C
|
|
||||||
d.Mul(d, big.NewInt(2)) //2*((X1+B)²-A-C)
|
|
||||||
|
|
||||||
e := new(big.Int).Mul(big.NewInt(3), a) //3*A
|
|
||||||
f := new(big.Int).Mul(e, e) //E²
|
|
||||||
|
|
||||||
x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D
|
|
||||||
x3.Sub(f, x3) //F-2*D
|
|
||||||
x3.Mod(x3, bitCurve.P)
|
|
||||||
|
|
||||||
y3 := new(big.Int).Sub(d, x3) //D-X3
|
|
||||||
y3.Mul(e, y3) //E*(D-X3)
|
|
||||||
y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C
|
|
||||||
y3.Mod(y3, bitCurve.P)
|
|
||||||
|
|
||||||
z3 := new(big.Int).Mul(y, z) //Y1*Z1
|
|
||||||
z3.Mul(big.NewInt(2), z3) //3*Y1*Z1
|
|
||||||
z3.Mod(z3, bitCurve.P)
|
|
||||||
|
|
||||||
return x3, y3, z3
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: double check if it is okay
|
|
||||||
// ScalarMult returns k*(Bx,By) where k is a number in big-endian form.
|
|
||||||
func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
|
|
||||||
// We have a slight problem in that the identity of the group (the
|
|
||||||
// point at infinity) cannot be represented in (x, y) form on a finite
|
|
||||||
// machine. Thus the standard add/double algorithm has to be tweaked
|
|
||||||
// slightly: our initial state is not the identity, but x, and we
|
|
||||||
// ignore the first true bit in |k|. If we don't find any true bits in
|
|
||||||
// |k|, then we return nil, nil, because we cannot return the identity
|
|
||||||
// element.
|
|
||||||
|
|
||||||
Bz := new(big.Int).SetInt64(1)
|
|
||||||
x := Bx
|
|
||||||
y := By
|
|
||||||
z := Bz
|
|
||||||
|
|
||||||
seenFirstTrue := false
|
|
||||||
for _, byte := range k {
|
|
||||||
for bitNum := 0; bitNum < 8; bitNum++ {
|
|
||||||
if seenFirstTrue {
|
|
||||||
x, y, z = bitCurve.doubleJacobian(x, y, z)
|
|
||||||
}
|
|
||||||
if byte&0x80 == 0x80 {
|
|
||||||
if !seenFirstTrue {
|
|
||||||
seenFirstTrue = true
|
|
||||||
} else {
|
|
||||||
x, y, z = bitCurve.addJacobian(Bx, By, Bz, x, y, z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte <<= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !seenFirstTrue {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return bitCurve.affineFromJacobian(x, y, z)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScalarBaseMult returns k*G, where G is the base point of the group and k is
|
|
||||||
// an integer in big-endian form.
|
|
||||||
func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
|
|
||||||
return bitCurve.ScalarMult(bitCurve.Gx, bitCurve.Gy, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f}
|
|
||||||
|
|
||||||
// TODO: double check if it is okay
|
|
||||||
// GenerateKey returns a public/private key pair. The private key is generated
|
|
||||||
// using the given reader, which must return random data.
|
|
||||||
func (bitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) {
|
|
||||||
byteLen := (bitCurve.BitSize + 7) >> 3
|
|
||||||
priv = make([]byte, byteLen)
|
|
||||||
|
|
||||||
for x == nil {
|
|
||||||
_, err = io.ReadFull(rand, priv)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// We have to mask off any excess bits in the case that the size of the
|
|
||||||
// underlying field is not a whole number of bytes.
|
|
||||||
priv[0] &= mask[bitCurve.BitSize%8]
|
|
||||||
// This is because, in tests, rand will return all zeros and we don't
|
|
||||||
// want to get the point at infinity and loop forever.
|
|
||||||
priv[1] ^= 0x42
|
|
||||||
x, y = bitCurve.ScalarBaseMult(priv)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal converts a point into the form specified in section 4.3.6 of ANSI
|
|
||||||
// X9.62.
|
|
||||||
func (bitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
|
|
||||||
byteLen := (bitCurve.BitSize + 7) >> 3
|
|
||||||
|
|
||||||
ret := make([]byte, 1+2*byteLen)
|
|
||||||
ret[0] = 4 // uncompressed point
|
|
||||||
|
|
||||||
xBytes := x.Bytes()
|
|
||||||
copy(ret[1+byteLen-len(xBytes):], xBytes)
|
|
||||||
yBytes := y.Bytes()
|
|
||||||
copy(ret[1+2*byteLen-len(yBytes):], yBytes)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On
|
|
||||||
// error, x = nil.
|
|
||||||
func (bitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
|
|
||||||
byteLen := (bitCurve.BitSize + 7) >> 3
|
|
||||||
if len(data) != 1+2*byteLen {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if data[0] != 4 { // uncompressed form
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x = new(big.Int).SetBytes(data[1 : 1+byteLen])
|
|
||||||
y = new(big.Int).SetBytes(data[1+byteLen:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//curve parameters taken from:
|
|
||||||
//http://www.secg.org/collateral/sec2_final.pdf
|
|
||||||
|
|
||||||
var initonce sync.Once
|
|
||||||
var secp160k1 *BitCurve
|
|
||||||
var secp192k1 *BitCurve
|
|
||||||
var secp224k1 *BitCurve
|
|
||||||
var secp256k1 *BitCurve
|
|
||||||
|
|
||||||
func initAll() {
|
|
||||||
initS160()
|
|
||||||
initS192()
|
|
||||||
initS224()
|
|
||||||
initS256()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initS160() {
|
|
||||||
// See SEC 2 section 2.4.1
|
|
||||||
secp160k1 = new(BitCurve)
|
|
||||||
secp160k1.Name = "secp160k1"
|
|
||||||
secp160k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73", 16)
|
|
||||||
secp160k1.N, _ = new(big.Int).SetString("0100000000000000000001B8FA16DFAB9ACA16B6B3", 16)
|
|
||||||
secp160k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000007", 16)
|
|
||||||
secp160k1.Gx, _ = new(big.Int).SetString("3B4C382CE37AA192A4019E763036F4F5DD4D7EBB", 16)
|
|
||||||
secp160k1.Gy, _ = new(big.Int).SetString("938CF935318FDCED6BC28286531733C3F03C4FEE", 16)
|
|
||||||
secp160k1.BitSize = 160
|
|
||||||
}
|
|
||||||
|
|
||||||
func initS192() {
|
|
||||||
// See SEC 2 section 2.5.1
|
|
||||||
secp192k1 = new(BitCurve)
|
|
||||||
secp192k1.Name = "secp192k1"
|
|
||||||
secp192k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37", 16)
|
|
||||||
secp192k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D", 16)
|
|
||||||
secp192k1.B, _ = new(big.Int).SetString("000000000000000000000000000000000000000000000003", 16)
|
|
||||||
secp192k1.Gx, _ = new(big.Int).SetString("DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D", 16)
|
|
||||||
secp192k1.Gy, _ = new(big.Int).SetString("9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D", 16)
|
|
||||||
secp192k1.BitSize = 192
|
|
||||||
}
|
|
||||||
|
|
||||||
func initS224() {
|
|
||||||
// See SEC 2 section 2.6.1
|
|
||||||
secp224k1 = new(BitCurve)
|
|
||||||
secp224k1.Name = "secp224k1"
|
|
||||||
secp224k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D", 16)
|
|
||||||
secp224k1.N, _ = new(big.Int).SetString("010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7", 16)
|
|
||||||
secp224k1.B, _ = new(big.Int).SetString("00000000000000000000000000000000000000000000000000000005", 16)
|
|
||||||
secp224k1.Gx, _ = new(big.Int).SetString("A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C", 16)
|
|
||||||
secp224k1.Gy, _ = new(big.Int).SetString("7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5", 16)
|
|
||||||
secp224k1.BitSize = 224
|
|
||||||
}
|
|
||||||
|
|
||||||
func initS256() {
|
|
||||||
// See SEC 2 section 2.7.1
|
|
||||||
secp256k1 = new(BitCurve)
|
|
||||||
secp256k1.Name = "secp256k1"
|
|
||||||
secp256k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
|
|
||||||
secp256k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
|
|
||||||
secp256k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16)
|
|
||||||
secp256k1.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
|
|
||||||
secp256k1.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
|
|
||||||
secp256k1.BitSize = 256
|
|
||||||
}
|
|
||||||
|
|
||||||
// S160 returns a BitCurve which implements secp160k1 (see SEC 2 section 2.4.1)
|
|
||||||
func S160() *BitCurve {
|
|
||||||
initonce.Do(initAll)
|
|
||||||
return secp160k1
|
|
||||||
}
|
|
||||||
|
|
||||||
// S192 returns a BitCurve which implements secp192k1 (see SEC 2 section 2.5.1)
|
|
||||||
func S192() *BitCurve {
|
|
||||||
initonce.Do(initAll)
|
|
||||||
return secp192k1
|
|
||||||
}
|
|
||||||
|
|
||||||
// S224 returns a BitCurve which implements secp224k1 (see SEC 2 section 2.6.1)
|
|
||||||
func S224() *BitCurve {
|
|
||||||
initonce.Do(initAll)
|
|
||||||
return secp224k1
|
|
||||||
}
|
|
||||||
|
|
||||||
// S256 returns a BitCurve which implements bitcurves (see SEC 2 section 2.7.1)
|
|
||||||
func S256() *BitCurve {
|
|
||||||
initonce.Do(initAll)
|
|
||||||
return secp256k1
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
// Package brainpool implements Brainpool elliptic curves.
|
|
||||||
// Implementation of rcurves is from github.com/ebfe/brainpool
|
|
||||||
// Note that these curves are implemented with naive, non-constant time operations
|
|
||||||
// and are likely not suitable for environments where timing attacks are a concern.
|
|
||||||
package brainpool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/elliptic"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
once sync.Once
|
|
||||||
p256t1, p384t1, p512t1 *elliptic.CurveParams
|
|
||||||
p256r1, p384r1, p512r1 *rcurve
|
|
||||||
)
|
|
||||||
|
|
||||||
func initAll() {
|
|
||||||
initP256t1()
|
|
||||||
initP384t1()
|
|
||||||
initP512t1()
|
|
||||||
initP256r1()
|
|
||||||
initP384r1()
|
|
||||||
initP512r1()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initP256t1() {
|
|
||||||
p256t1 = &elliptic.CurveParams{Name: "brainpoolP256t1"}
|
|
||||||
p256t1.P, _ = new(big.Int).SetString("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", 16)
|
|
||||||
p256t1.N, _ = new(big.Int).SetString("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7", 16)
|
|
||||||
p256t1.B, _ = new(big.Int).SetString("662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04", 16)
|
|
||||||
p256t1.Gx, _ = new(big.Int).SetString("A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4", 16)
|
|
||||||
p256t1.Gy, _ = new(big.Int).SetString("2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE", 16)
|
|
||||||
p256t1.BitSize = 256
|
|
||||||
}
|
|
||||||
|
|
||||||
func initP256r1() {
|
|
||||||
twisted := p256t1
|
|
||||||
params := &elliptic.CurveParams{
|
|
||||||
Name: "brainpoolP256r1",
|
|
||||||
P: twisted.P,
|
|
||||||
N: twisted.N,
|
|
||||||
BitSize: twisted.BitSize,
|
|
||||||
}
|
|
||||||
params.Gx, _ = new(big.Int).SetString("8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262", 16)
|
|
||||||
params.Gy, _ = new(big.Int).SetString("547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997", 16)
|
|
||||||
z, _ := new(big.Int).SetString("3E2D4BD9597B58639AE7AA669CAB9837CF5CF20A2C852D10F655668DFC150EF0", 16)
|
|
||||||
p256r1 = newrcurve(twisted, params, z)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initP384t1() {
|
|
||||||
p384t1 = &elliptic.CurveParams{Name: "brainpoolP384t1"}
|
|
||||||
p384t1.P, _ = new(big.Int).SetString("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", 16)
|
|
||||||
p384t1.N, _ = new(big.Int).SetString("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565", 16)
|
|
||||||
p384t1.B, _ = new(big.Int).SetString("7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B88805CED70355A33B471EE", 16)
|
|
||||||
p384t1.Gx, _ = new(big.Int).SetString("18DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946A5F54D8D0AA2F418808CC", 16)
|
|
||||||
p384t1.Gy, _ = new(big.Int).SetString("25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC2B2912675BF5B9E582928", 16)
|
|
||||||
p384t1.BitSize = 384
|
|
||||||
}
|
|
||||||
|
|
||||||
func initP384r1() {
|
|
||||||
twisted := p384t1
|
|
||||||
params := &elliptic.CurveParams{
|
|
||||||
Name: "brainpoolP384r1",
|
|
||||||
P: twisted.P,
|
|
||||||
N: twisted.N,
|
|
||||||
BitSize: twisted.BitSize,
|
|
||||||
}
|
|
||||||
params.Gx, _ = new(big.Int).SetString("1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D646AAEF87B2E247D4AF1E", 16)
|
|
||||||
params.Gy, _ = new(big.Int).SetString("8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315", 16)
|
|
||||||
z, _ := new(big.Int).SetString("41DFE8DD399331F7166A66076734A89CD0D2BCDB7D068E44E1F378F41ECBAE97D2D63DBC87BCCDDCCC5DA39E8589291C", 16)
|
|
||||||
p384r1 = newrcurve(twisted, params, z)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initP512t1() {
|
|
||||||
p512t1 = &elliptic.CurveParams{Name: "brainpoolP512t1"}
|
|
||||||
p512t1.P, _ = new(big.Int).SetString("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", 16)
|
|
||||||
p512t1.N, _ = new(big.Int).SetString("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069", 16)
|
|
||||||
p512t1.B, _ = new(big.Int).SetString("7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA2304976540F6450085F2DAE145C22553B465763689180EA2571867423E", 16)
|
|
||||||
p512t1.Gx, _ = new(big.Int).SetString("640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CDB3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA", 16)
|
|
||||||
p512t1.Gy, _ = new(big.Int).SetString("5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEEF216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332", 16)
|
|
||||||
p512t1.BitSize = 512
|
|
||||||
}
|
|
||||||
|
|
||||||
func initP512r1() {
|
|
||||||
twisted := p512t1
|
|
||||||
params := &elliptic.CurveParams{
|
|
||||||
Name: "brainpoolP512r1",
|
|
||||||
P: twisted.P,
|
|
||||||
N: twisted.N,
|
|
||||||
BitSize: twisted.BitSize,
|
|
||||||
}
|
|
||||||
params.Gx, _ = new(big.Int).SetString("81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822", 16)
|
|
||||||
params.Gy, _ = new(big.Int).SetString("7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892", 16)
|
|
||||||
z, _ := new(big.Int).SetString("12EE58E6764838B69782136F0F2D3BA06E27695716054092E60A80BEDB212B64E585D90BCE13761F85C3F1D2A64E3BE8FEA2220F01EBA5EEB0F35DBD29D922AB", 16)
|
|
||||||
p512r1 = newrcurve(twisted, params, z)
|
|
||||||
}
|
|
||||||
|
|
||||||
// P256t1 returns a Curve which implements Brainpool P256t1 (see RFC 5639, section 3.4)
|
|
||||||
func P256t1() elliptic.Curve {
|
|
||||||
once.Do(initAll)
|
|
||||||
return p256t1
|
|
||||||
}
|
|
||||||
|
|
||||||
// P256r1 returns a Curve which implements Brainpool P256r1 (see RFC 5639, section 3.4)
|
|
||||||
func P256r1() elliptic.Curve {
|
|
||||||
once.Do(initAll)
|
|
||||||
return p256r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// P384t1 returns a Curve which implements Brainpool P384t1 (see RFC 5639, section 3.6)
|
|
||||||
func P384t1() elliptic.Curve {
|
|
||||||
once.Do(initAll)
|
|
||||||
return p384t1
|
|
||||||
}
|
|
||||||
|
|
||||||
// P384r1 returns a Curve which implements Brainpool P384r1 (see RFC 5639, section 3.6)
|
|
||||||
func P384r1() elliptic.Curve {
|
|
||||||
once.Do(initAll)
|
|
||||||
return p384r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// P512t1 returns a Curve which implements Brainpool P512t1 (see RFC 5639, section 3.7)
|
|
||||||
func P512t1() elliptic.Curve {
|
|
||||||
once.Do(initAll)
|
|
||||||
return p512t1
|
|
||||||
}
|
|
||||||
|
|
||||||
// P512r1 returns a Curve which implements Brainpool P512r1 (see RFC 5639, section 3.7)
|
|
||||||
func P512r1() elliptic.Curve {
|
|
||||||
once.Do(initAll)
|
|
||||||
return p512r1
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package brainpool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/elliptic"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ elliptic.Curve = (*rcurve)(nil)
|
|
||||||
|
|
||||||
type rcurve struct {
|
|
||||||
twisted elliptic.Curve
|
|
||||||
params *elliptic.CurveParams
|
|
||||||
z *big.Int
|
|
||||||
zinv *big.Int
|
|
||||||
z2 *big.Int
|
|
||||||
z3 *big.Int
|
|
||||||
zinv2 *big.Int
|
|
||||||
zinv3 *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
two = big.NewInt(2)
|
|
||||||
three = big.NewInt(3)
|
|
||||||
)
|
|
||||||
|
|
||||||
func newrcurve(twisted elliptic.Curve, params *elliptic.CurveParams, z *big.Int) *rcurve {
|
|
||||||
zinv := new(big.Int).ModInverse(z, params.P)
|
|
||||||
return &rcurve{
|
|
||||||
twisted: twisted,
|
|
||||||
params: params,
|
|
||||||
z: z,
|
|
||||||
zinv: zinv,
|
|
||||||
z2: new(big.Int).Exp(z, two, params.P),
|
|
||||||
z3: new(big.Int).Exp(z, three, params.P),
|
|
||||||
zinv2: new(big.Int).Exp(zinv, two, params.P),
|
|
||||||
zinv3: new(big.Int).Exp(zinv, three, params.P),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) toTwisted(x, y *big.Int) (*big.Int, *big.Int) {
|
|
||||||
var tx, ty big.Int
|
|
||||||
tx.Mul(x, curve.z2)
|
|
||||||
tx.Mod(&tx, curve.params.P)
|
|
||||||
ty.Mul(y, curve.z3)
|
|
||||||
ty.Mod(&ty, curve.params.P)
|
|
||||||
return &tx, &ty
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) fromTwisted(tx, ty *big.Int) (*big.Int, *big.Int) {
|
|
||||||
var x, y big.Int
|
|
||||||
x.Mul(tx, curve.zinv2)
|
|
||||||
x.Mod(&x, curve.params.P)
|
|
||||||
y.Mul(ty, curve.zinv3)
|
|
||||||
y.Mod(&y, curve.params.P)
|
|
||||||
return &x, &y
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) Params() *elliptic.CurveParams {
|
|
||||||
return curve.params
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) IsOnCurve(x, y *big.Int) bool {
|
|
||||||
return curve.twisted.IsOnCurve(curve.toTwisted(x, y))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) Add(x1, y1, x2, y2 *big.Int) (x, y *big.Int) {
|
|
||||||
tx1, ty1 := curve.toTwisted(x1, y1)
|
|
||||||
tx2, ty2 := curve.toTwisted(x2, y2)
|
|
||||||
return curve.fromTwisted(curve.twisted.Add(tx1, ty1, tx2, ty2))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) Double(x1, y1 *big.Int) (x, y *big.Int) {
|
|
||||||
return curve.fromTwisted(curve.twisted.Double(curve.toTwisted(x1, y1)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) ScalarMult(x1, y1 *big.Int, scalar []byte) (x, y *big.Int) {
|
|
||||||
tx1, ty1 := curve.toTwisted(x1, y1)
|
|
||||||
return curve.fromTwisted(curve.twisted.ScalarMult(tx1, ty1, scalar))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (curve *rcurve) ScalarBaseMult(scalar []byte) (x, y *big.Int) {
|
|
||||||
return curve.fromTwisted(curve.twisted.ScalarBaseMult(scalar))
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package eax
|
|
||||||
|
|
||||||
// Test vectors from
|
|
||||||
// https://web.cs.ucdavis.edu/~rogaway/papers/eax.pdf
|
|
||||||
var testVectors = []struct {
|
|
||||||
msg, key, nonce, header, ciphertext string
|
|
||||||
}{
|
|
||||||
{"",
|
|
||||||
"233952DEE4D5ED5F9B9C6D6FF80FF478",
|
|
||||||
"62EC67F9C3A4A407FCB2A8C49031A8B3",
|
|
||||||
"6BFB914FD07EAE6B",
|
|
||||||
"E037830E8389F27B025A2D6527E79D01"},
|
|
||||||
{"F7FB",
|
|
||||||
"91945D3F4DCBEE0BF45EF52255F095A4",
|
|
||||||
"BECAF043B0A23D843194BA972C66DEBD",
|
|
||||||
"FA3BFD4806EB53FA",
|
|
||||||
"19DD5C4C9331049D0BDAB0277408F67967E5"},
|
|
||||||
{"1A47CB4933",
|
|
||||||
"01F74AD64077F2E704C0F60ADA3DD523",
|
|
||||||
"70C3DB4F0D26368400A10ED05D2BFF5E",
|
|
||||||
"234A3463C1264AC6",
|
|
||||||
"D851D5BAE03A59F238A23E39199DC9266626C40F80"},
|
|
||||||
{"481C9E39B1",
|
|
||||||
"D07CF6CBB7F313BDDE66B727AFD3C5E8",
|
|
||||||
"8408DFFF3C1A2B1292DC199E46B7D617",
|
|
||||||
"33CCE2EABFF5A79D",
|
|
||||||
"632A9D131AD4C168A4225D8E1FF755939974A7BEDE"},
|
|
||||||
{"40D0C07DA5E4",
|
|
||||||
"35B6D0580005BBC12B0587124557D2C2",
|
|
||||||
"FDB6B06676EEDC5C61D74276E1F8E816",
|
|
||||||
"AEB96EAEBE2970E9",
|
|
||||||
"071DFE16C675CB0677E536F73AFE6A14B74EE49844DD"},
|
|
||||||
{"4DE3B35C3FC039245BD1FB7D",
|
|
||||||
"BD8E6E11475E60B268784C38C62FEB22",
|
|
||||||
"6EAC5C93072D8E8513F750935E46DA1B",
|
|
||||||
"D4482D1CA78DCE0F",
|
|
||||||
"835BB4F15D743E350E728414ABB8644FD6CCB86947C5E10590210A4F"},
|
|
||||||
{"8B0A79306C9CE7ED99DAE4F87F8DD61636",
|
|
||||||
"7C77D6E813BED5AC98BAA417477A2E7D",
|
|
||||||
"1A8C98DCD73D38393B2BF1569DEEFC19",
|
|
||||||
"65D2017990D62528",
|
|
||||||
"02083E3979DA014812F59F11D52630DA30137327D10649B0AA6E1C181DB617D7F2"},
|
|
||||||
{"1BDA122BCE8A8DBAF1877D962B8592DD2D56",
|
|
||||||
"5FFF20CAFAB119CA2FC73549E20F5B0D",
|
|
||||||
"DDE59B97D722156D4D9AFF2BC7559826",
|
|
||||||
"54B9F04E6A09189A",
|
|
||||||
"2EC47B2C4954A489AFC7BA4897EDCDAE8CC33B60450599BD02C96382902AEF7F832A"},
|
|
||||||
{"6CF36720872B8513F6EAB1A8A44438D5EF11",
|
|
||||||
"A4A4782BCFFD3EC5E7EF6D8C34A56123",
|
|
||||||
"B781FCF2F75FA5A8DE97A9CA48E522EC",
|
|
||||||
"899A175897561D7E",
|
|
||||||
"0DE18FD0FDD91E7AF19F1D8EE8733938B1E8E7F6D2231618102FDB7FE55FF1991700"},
|
|
||||||
{"CA40D7446E545FFAED3BD12A740A659FFBBB3CEAB7",
|
|
||||||
"8395FCF1E95BEBD697BD010BC766AAC3",
|
|
||||||
"22E7ADD93CFC6393C57EC0B3C17D6B44",
|
|
||||||
"126735FCC320D25A",
|
|
||||||
"CB8920F87A6C75CFF39627B56E3ED197C552D295A7CFC46AFC253B4652B1AF3795B124AB6E"},
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
// These vectors include key length in {128, 192, 256}, tag size 128, and
|
|
||||||
// random nonce, header, and plaintext lengths.
|
|
||||||
|
|
||||||
// This file was automatically generated.
|
|
||||||
|
|
||||||
package eax
|
|
||||||
|
|
||||||
var randomVectors = []struct {
|
|
||||||
key, nonce, header, plaintext, ciphertext string
|
|
||||||
}{
|
|
||||||
{"DFDE093F36B0356E5A81F609786982E3",
|
|
||||||
"1D8AC604419001816905BA72B14CED7E",
|
|
||||||
"152A1517A998D7A24163FCDD146DE81AC347C8B97088F502093C1ABB8F6E33D9A219C34D7603A18B1F5ABE02E56661B7D7F67E81EC08C1302EF38D80A859486D450E94A4F26AD9E68EEBBC0C857A0FC5CF9E641D63D565A7E361BC8908F5A8DC8FD6",
|
|
||||||
"1C8EAAB71077FE18B39730A3156ADE29C5EE824C7EE86ED2A253B775603FB237116E654F6FEC588DD27F523A0E01246FE73FE348491F2A8E9ABC6CA58D663F71CDBCF4AD798BE46C42AE6EE8B599DB44A1A48D7BBBBA0F7D2750181E1C5E66967F7D57CBD30AFBDA5727",
|
|
||||||
"79E7E150934BBEBF7013F61C60462A14D8B15AF7A248AFB8A344EF021C1500E16666891D6E973D8BB56B71A371F12CA34660C4410C016982B20F547E3762A58B7BF4F20236CADCF559E2BE7D783B13723B2741FC7CDC8997D839E39A3DDD2BADB96743DD7049F1BDB0516A262869915B3F70498AFB7B191BF960"},
|
|
||||||
{"F10619EF02E5D94D7550EB84ED364A21",
|
|
||||||
"8DC0D4F2F745BBAE835CC5574B942D20",
|
|
||||||
"FE561358F2E8DF7E1024FF1AE9A8D36EBD01352214505CB99D644777A8A1F6027FA2BDBFC529A9B91136D5F2416CFC5F0F4EC3A1AFD32BDDA23CA504C5A5CB451785FABF4DFE4CD50D817491991A60615B30286361C100A95D1712F2A45F8E374461F4CA2B",
|
|
||||||
"D7B5A971FC219631D30EFC3664AE3127D9CF3097DAD9C24AC7905D15E8D9B25B026B31D68CAE00975CDB81EB1FD96FD5E1A12E2BB83FA25F1B1D91363457657FC03875C27F2946C5",
|
|
||||||
"2F336ED42D3CC38FC61660C4CD60BA4BD438B05F5965D8B7B399D2E7167F5D34F792D318F94DB15D67463AC449E13D568CC09BFCE32A35EE3EE96A041927680AE329811811E27F2D1E8E657707AF99BA96D13A478D695D59"},
|
|
||||||
{"429F514EFC64D98A698A9247274CFF45",
|
|
||||||
"976AA5EB072F912D126ACEBC954FEC38",
|
|
||||||
"A71D89DC5B6CEDBB7451A27C3C2CAE09126DB4C421",
|
|
||||||
"5632FE62AB1DC549D54D3BC3FC868ACCEDEFD9ECF5E9F8",
|
|
||||||
"848AE4306CA8C7F416F8707625B7F55881C0AB430353A5C967CDA2DA787F581A70E34DBEBB2385"},
|
|
||||||
{"398138F309085F47F8457CDF53895A63",
|
|
||||||
"F8A8A7F2D28E5FFF7BBC2F24353F7A36",
|
|
||||||
"5D633C21BA7764B8855CAB586F3746E236AD486039C83C6B56EFA9C651D38A41D6B20DAEE3418BFEA44B8BD6",
|
|
||||||
"A3BBAA91920AF5E10659818B1B3B300AC79BFC129C8329E75251F73A66D3AE0128EB91D5031E0A65C329DB7D1E9C0493E268",
|
|
||||||
"D078097267606E5FB07CFB7E2B4B718172A82C6A4CEE65D549A4DFB9838003BD2FBF64A7A66988AC1A632FD88F9E9FBB57C5A78AD2E086EACBA3DB68511D81C2970A"},
|
|
||||||
{"7A4151EBD3901B42CBA45DAFB2E931BA",
|
|
||||||
"0FC88ACEE74DD538040321C330974EB8",
|
|
||||||
"250464FB04733BAB934C59E6AD2D6AE8D662CBCFEFBE61E5A308D4211E58C4C25935B72C69107722E946BFCBF416796600542D76AEB73F2B25BF53BAF97BDEB36ED3A7A51C31E7F170EB897457E7C17571D1BA0A908954E9",
|
|
||||||
"88C41F3EBEC23FAB8A362D969CAC810FAD4F7CA6A7F7D0D44F060F92E37E1183768DD4A8C733F71C96058D362A39876D183B86C103DE",
|
|
||||||
"74A25B2182C51096D48A870D80F18E1CE15867778E34FCBA6BD7BFB3739FDCD42AD0F2D9F4EBA29085285C6048C15BCE5E5166F1F962D3337AA88E6062F05523029D0A7F0BF9"},
|
|
||||||
{"BFB147E1CD5459424F8C0271FC0E0DC5",
|
|
||||||
"EABCC126442BF373969EA3015988CC45",
|
|
||||||
"4C0880E1D71AA2C7",
|
|
||||||
"BE1B5EC78FBF73E7A6682B21BA7E0E5D2D1C7ABE",
|
|
||||||
"5660D7C1380E2F306895B1402CB2D6C37876504276B414D120F4CF92FDDDBB293A238EA0"},
|
|
||||||
{"595DD6F52D18BC2CA8EB4EDAA18D9FA3",
|
|
||||||
"0F84B5D36CF4BC3B863313AF3B4D2E97",
|
|
||||||
"30AE6CC5F99580F12A779D98BD379A60948020C0B6FBD5746B30BA3A15C6CD33DAF376C70A9F15B6C0EB410A93161F7958AE23",
|
|
||||||
"8EF3687A1642B070970B0B91462229D1D76ABC154D18211F7152AA9FF368",
|
|
||||||
"317C1DDB11417E5A9CC4DDE7FDFF6659A5AC4B31DE025212580A05CDAC6024D3E4AE7C2966E52B9129E9ECDBED86"},
|
|
||||||
{"44E6F2DC8FDC778AD007137D11410F50",
|
|
||||||
"270A237AD977F7187AA6C158A0BAB24F",
|
|
||||||
"509B0F0EB12E2AA5C5BA2DE553C07FAF4CE0C9E926531AA709A3D6224FCB783ACCF1559E10B1123EBB7D52E8AB54E6B5352A9ED0D04124BF0E9D9BACFD7E32B817B2E625F5EE94A64EDE9E470DE7FE6886C19B294F9F828209FE257A78",
|
|
||||||
"8B3D7815DF25618A5D0C55A601711881483878F113A12EC36CF64900549A3199555528559DC118F789788A55FAFD944E6E99A9CA3F72F238CD3F4D88223F7A745992B3FAED1848",
|
|
||||||
"1CC00D79F7AD82FDA71B58D286E5F34D0CC4CEF30704E771CC1E50746BDF83E182B078DB27149A42BAE619DF0F85B0B1090AD55D3B4471B0D6F6ECCD09C8F876B30081F0E7537A9624F8AAF29DA85E324122EFB4D68A56"},
|
|
||||||
{"BB7BC352A03044B4428D8DBB4B0701FDEC4649FD17B81452",
|
|
||||||
"8B4BBE26CCD9859DCD84884159D6B0A4",
|
|
||||||
"2212BEB0E78E0F044A86944CF33C8D5C80D9DBE1034BF3BCF73611835C7D3A52F5BD2D81B68FD681B68540A496EE5DA16FD8AC8824E60E1EC2042BE28FB0BFAD4E4B03596446BDD8C37D936D9B3D5295BE19F19CF5ACE1D33A46C952CE4DE5C12F92C1DD051E04AEED",
|
|
||||||
"9037234CC44FFF828FABED3A7084AF40FA7ABFF8E0C0EFB57A1CC361E18FC4FAC1AB54F3ABFE9FF77263ACE16C3A",
|
|
||||||
"A9391B805CCD956081E0B63D282BEA46E7025126F1C1631239C33E92AA6F92CD56E5A4C56F00FF9658E93D48AF4EF0EF81628E34AD4DB0CDAEDCD2A17EE7"},
|
|
||||||
{"99C0AD703196D2F60A74E6B378B838B31F82EA861F06FC4E",
|
|
||||||
"92745C018AA708ECFEB1667E9F3F1B01",
|
|
||||||
"828C69F376C0C0EC651C67749C69577D589EE39E51404D80EBF70C8660A8F5FD375473F4A7C611D59CB546A605D67446CE2AA844135FCD78BB5FBC90222A00D42920BB1D7EEDFB0C4672554F583EF23184F89063CDECBE482367B5F9AF3ACBC3AF61392BD94CBCD9B64677",
|
|
||||||
"A879214658FD0A5B0E09836639BF82E05EC7A5EF71D4701934BDA228435C68AC3D5CEB54997878B06A655EEACEFB1345C15867E7FE6C6423660C8B88DF128EBD6BCD85118DBAE16E9252FFB204324E5C8F38CA97759BDBF3CB0083",
|
|
||||||
"51FE87996F194A2585E438B023B345439EA60D1AEBED4650CDAF48A4D4EEC4FC77DC71CC4B09D3BEEF8B7B7AF716CE2B4EFFB3AC9E6323C18AC35E0AA6E2BBBC8889490EB6226C896B0D105EAB42BFE7053CCF00ED66BA94C1BA09A792AA873F0C3B26C5C5F9A936E57B25"},
|
|
||||||
{"7086816D00D648FB8304AA8C9E552E1B69A9955FB59B25D1",
|
|
||||||
"0F45CF7F0BF31CCEB85D9DA10F4D749F",
|
|
||||||
"93F27C60A417D9F0669E86ACC784FC8917B502DAF30A6338F11B30B94D74FEFE2F8BE1BBE2EAD10FAB7EED3C6F72B7C3ECEE1937C32ED4970A6404E139209C05",
|
|
||||||
"877F046601F3CBE4FB1491943FA29487E738F94B99AF206262A1D6FF856C9AA0B8D4D08A54370C98F8E88FA3DCC2B14C1F76D71B2A4C7963AEE8AF960464C5BEC8357AD00DC8",
|
|
||||||
"FE96906B895CE6A8E72BC72344E2C8BB3C63113D70EAFA26C299BAFE77A8A6568172EB447FB3E86648A0AF3512DEB1AAC0819F3EC553903BF28A9FB0F43411237A774BF9EE03E445D280FBB9CD12B9BAAB6EF5E52691"},
|
|
||||||
{"062F65A896D5BF1401BADFF70E91B458E1F9BD4888CB2E4D",
|
|
||||||
"5B11EA1D6008EBB41CF892FCA5B943D1",
|
|
||||||
"BAF4FF5C8242",
|
|
||||||
"A8870E091238355984EB2F7D61A865B9170F440BFF999A5993DD41A10F4440D21FF948DDA2BF663B2E03AC3324492DC5E40262ECC6A65C07672353BE23E7FB3A9D79FF6AA38D97960905A38DECC312CB6A59E5467ECF06C311CD43ADC0B543EDF34FE8BE611F176460D5627CA51F8F8D9FED71F55C",
|
|
||||||
"B10E127A632172CF8AA7539B140D2C9C2590E6F28C3CB892FC498FCE56A34F732FBFF32E79C7B9747D9094E8635A0C084D6F0247F9768FB5FF83493799A9BEC6C39572120C40E9292C8C947AE8573462A9108C36D9D7112E6995AE5867E6C8BB387D1C5D4BEF524F391B9FD9F0A3B4BFA079E915BCD920185CFD38D114C558928BD7D47877"},
|
|
||||||
{"38A8E45D6D705A11AF58AED5A1344896998EACF359F2E26A",
|
|
||||||
"FD82B5B31804FF47D44199B533D0CF84",
|
|
||||||
"DE454D4E62FE879F2050EE3E25853623D3E9AC52EEC1A1779A48CFAF5ECA0BFDE44749391866D1",
|
|
||||||
"B804",
|
|
||||||
"164BB965C05EBE0931A1A63293EDF9C38C27"},
|
|
||||||
{"34C33C97C6D7A0850DA94D78A58DC61EC717CD7574833068",
|
|
||||||
"343BE00DA9483F05C14F2E9EB8EA6AE8",
|
|
||||||
"78312A43EFDE3CAE34A65796FF059A3FE15304EEA5CF1D9306949FE5BF3349D4977D4EBE76C040FE894C5949E4E4D6681153DA87FB9AC5062063CA2EA183566343362370944CE0362D25FC195E124FD60E8682E665D13F2229DDA3E4B2CB1DCA",
|
|
||||||
"CC11BB284B1153578E4A5ED9D937B869DAF00F5B1960C23455CA9CC43F486A3BE0B66254F1041F04FDF459C8640465B6E1D2CF899A381451E8E7FCB50CF87823BE77E24B132BBEEDC72E53369B275E1D8F49ECE59F4F215230AC4FE133FC80E4F634EE80BA4682B62C86",
|
|
||||||
"E7F703DC31A95E3A4919FF957836CB76C063D81702AEA4703E1C2BF30831E58C4609D626EC6810E12EAA5B930F049FF9EFC22C3E3F1EBD4A1FB285CB02A1AC5AD46B425199FC0A85670A5C4E3DAA9636C8F64C199F42F18AAC8EA7457FD377F322DD7752D7D01B946C8F0A97E6113F0D50106F319AFD291AAACE"},
|
|
||||||
{"C6ECF7F053573E403E61B83052A343D93CBCC179D1E835BE",
|
|
||||||
"E280E13D7367042E3AA09A80111B6184",
|
|
||||||
"21486C9D7A9647",
|
|
||||||
"5F2639AFA6F17931853791CD8C92382BBB677FD72D0AB1A080D0E49BFAA21810E963E4FACD422E92F65CBFAD5884A60CD94740DF31AF02F95AA57DA0C4401B0ED906",
|
|
||||||
"5C51DB20755302070C45F52E50128A67C8B2E4ED0EACB7E29998CCE2E8C289DD5655913EC1A51CC3AABE5CDC2402B2BE7D6D4BF6945F266FBD70BA9F37109067157AE7530678B45F64475D4EBFCB5FFF46A5"},
|
|
||||||
{"5EC6CF7401BC57B18EF154E8C38ACCA8959E57D2F3975FF5",
|
|
||||||
"656B41CB3F9CF8C08BAD7EBFC80BD225",
|
|
||||||
"6B817C2906E2AF425861A7EF59BA5801F143EE2A139EE72697CDE168B4",
|
|
||||||
"2C0E1DDC9B1E5389BA63845B18B1F8A1DB062037151BCC56EF7C21C0BB4DAE366636BBA975685D7CC5A94AFBE89C769016388C56FB7B57CE750A12B718A8BDCF70E80E8659A8330EFC8F86640F21735E8C80E23FE43ABF23507CE3F964AE4EC99D",
|
|
||||||
"ED780CF911E6D1AA8C979B889B0B9DC1ABE261832980BDBFB576901D9EF5AB8048998E31A15BE54B3E5845A4D136AD24D0BDA1C3006168DF2F8AC06729CB0818867398150020131D8F04EDF1923758C9EABB5F735DE5EA1758D4BC0ACFCA98AFD202E9839B8720253693B874C65586C6F0"},
|
|
||||||
{"C92F678EB2208662F5BCF3403EC05F5961E957908A3E79421E1D25FC19054153",
|
|
||||||
"DA0F3A40983D92F2D4C01FED33C7A192",
|
|
||||||
"2B6E9D26DB406A0FAB47608657AA10EFC2B4AA5F459B29FF85AC9A40BFFE7AEB04F77E9A11FAAA116D7F6D4DA417671A9AB02C588E0EF59CB1BFB4B1CC931B63A3B3A159FCEC97A04D1E6F0C7E6A9CEF6B0ABB04758A69F1FE754DF4C2610E8C46B6CF413BDB31351D55BEDCB7B4A13A1C98E10984475E0F2F957853",
|
|
||||||
"F37326A80E08",
|
|
||||||
"83519E53E321D334F7C10B568183775C0E9AAE55F806"},
|
|
||||||
{"6847E0491BE57E72995D186D50094B0B3593957A5146798FCE68B287B2FB37B5",
|
|
||||||
"3EE1182AEBB19A02B128F28E1D5F7F99",
|
|
||||||
"D9F35ABB16D776CE",
|
|
||||||
"DB7566ED8EA95BDF837F23DB277BAFBC5E70D1105ADFD0D9EF15475051B1EF94709C67DCA9F8D5",
|
|
||||||
"2CDCED0C9EBD6E2A508822A685F7DCD1CDD99E7A5FCA786C234E7F7F1D27EC49751AD5DCFA30C5EDA87C43CAE3B919B6BBCFE34C8EDA59"},
|
|
||||||
{"82B019673642C08388D3E42075A4D5D587558C229E4AB8F660E37650C4C41A0A",
|
|
||||||
"336F5D681E0410FAE7B607246092C6DC",
|
|
||||||
"D430CBD8FE435B64214E9E9CDC5DE99D31CFCFB8C10AA0587A49DF276611",
|
|
||||||
"998404153AD77003E1737EDE93ED79859EE6DCCA93CB40C4363AA817ABF2DBBD46E42A14A7183B6CC01E12A577888141363D0AE011EB6E8D28C0B235",
|
|
||||||
"9BEF69EEB60BD3D6065707B7557F25292A8872857CFBD24F2F3C088E4450995333088DA50FD9121221C504DF1D0CD5EFE6A12666C5D5BB12282CF4C19906E9CFAB97E9BDF7F49DC17CFC384B"},
|
|
||||||
{"747B2E269B1859F0622C15C8BAD6A725028B1F94B8DB7326948D1E6ED663A8BC",
|
|
||||||
"AB91F7245DDCE3F1C747872D47BE0A8A",
|
|
||||||
"3B03F786EF1DDD76E1D42646DA4CD2A5165DC5383CE86D1A0B5F13F910DC278A4E451EE0192CBA178E13B3BA27FDC7840DF73D2E104B",
|
|
||||||
"6B803F4701114F3E5FE21718845F8416F70F626303F545BE197189E0A2BA396F37CE06D389EB2658BC7D56D67868708F6D0D32",
|
|
||||||
"1570DDB0BCE75AA25D1957A287A2C36B1A5F2270186DA81BA6112B7F43B0F3D1D0ED072591DCF1F1C99BBB25621FC39B896FF9BD9413A2845363A9DCD310C32CF98E57"},
|
|
||||||
{"02E59853FB29AEDA0FE1C5F19180AD99A12FF2F144670BB2B8BADF09AD812E0A",
|
|
||||||
"C691294EF67CD04D1B9242AF83DD1421",
|
|
||||||
"879334DAE3",
|
|
||||||
"1E17F46A98FEF5CBB40759D95354",
|
|
||||||
"FED8C3FF27DDF6313AED444A2985B36CBA268AAD6AAC563C0BA28F6DB5DB"},
|
|
||||||
{"F6C1FB9B4188F2288FF03BD716023198C3582CF2A037FC2F29760916C2B7FCDB",
|
|
||||||
"4228DA0678CA3534588859E77DFF014C",
|
|
||||||
"D8153CAF35539A61DD8D05B3C9B44F01E564FB9348BCD09A1C23B84195171308861058F0A3CD2A55B912A3AAEE06FF4D356C77275828F2157C2FC7C115DA39E443210CCC56BEDB0CC99BBFB227ABD5CC454F4E7F547C7378A659EEB6A7E809101A84F866503CB18D4484E1FA09B3EC7FC75EB2E35270800AA7",
|
|
||||||
"23B660A779AD285704B12EC1C580387A47BEC7B00D452C6570",
|
|
||||||
"5AA642BBABA8E49849002A2FAF31DB8FC7773EFDD656E469CEC19B3206D4174C9A263D0A05484261F6"},
|
|
||||||
{"8FF6086F1FADB9A3FBE245EAC52640C43B39D43F89526BB5A6EBA47710931446",
|
|
||||||
"943188480C99437495958B0AE4831AA9",
|
|
||||||
"AD5CD0BDA426F6EBA23C8EB23DC73FF9FEC173355EDBD6C9344C4C4383F211888F7CE6B29899A6801DF6B38651A7C77150941A",
|
|
||||||
"80CD5EA8D7F81DDF5070B934937912E8F541A5301877528EB41AB60C020968D459960ED8FB73083329841A",
|
|
||||||
"ABAE8EB7F36FCA2362551E72DAC890BA1BB6794797E0FC3B67426EC9372726ED4725D379EA0AC9147E48DCD0005C502863C2C5358A38817C8264B5"},
|
|
||||||
{"A083B54E6B1FE01B65D42FCD248F97BB477A41462BBFE6FD591006C022C8FD84",
|
|
||||||
"B0490F5BD68A52459556B3749ACDF40E",
|
|
||||||
"8892E047DA5CFBBDF7F3CFCBD1BD21C6D4C80774B1826999234394BD3E513CC7C222BB40E1E3140A152F19B3802F0D036C24A590512AD0E8",
|
|
||||||
"D7B15752789DC94ED0F36778A5C7BBB207BEC32BAC66E702B39966F06E381E090C6757653C3D26A81EC6AD6C364D66867A334C91BB0B8A8A4B6EACDF0783D09010AEBA2DD2062308FE99CC1F",
|
|
||||||
"C071280A732ADC93DF272BF1E613B2BB7D46FC6665EF2DC1671F3E211D6BDE1D6ADDD28DF3AA2E47053FC8BB8AE9271EC8BC8B2CFFA320D225B451685B6D23ACEFDD241FE284F8ADC8DB07F456985B14330BBB66E0FB212213E05B3E"},
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
// Copyright (C) 2019 ProtonTech AG
|
|
||||||
// This file contains necessary tools for the aex and ocb packages.
|
|
||||||
//
|
|
||||||
// These functions SHOULD NOT be used elsewhere, since they are optimized for
|
|
||||||
// specific input nature in the EAX and OCB modes of operation.
|
|
||||||
|
|
||||||
package byteutil
|
|
||||||
|
|
||||||
// GfnDouble computes 2 * input in the field of 2^n elements.
|
|
||||||
// The irreducible polynomial in the finite field for n=128 is
|
|
||||||
// x^128 + x^7 + x^2 + x + 1 (equals 0x87)
|
|
||||||
// Constant-time execution in order to avoid side-channel attacks
|
|
||||||
func GfnDouble(input []byte) []byte {
|
|
||||||
if len(input) != 16 {
|
|
||||||
panic("Doubling in GFn only implemented for n = 128")
|
|
||||||
}
|
|
||||||
// If the first bit is zero, return 2L = L << 1
|
|
||||||
// Else return (L << 1) xor 0^120 10000111
|
|
||||||
shifted := ShiftBytesLeft(input)
|
|
||||||
shifted[15] ^= ((input[0] >> 7) * 0x87)
|
|
||||||
return shifted
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShiftBytesLeft outputs the byte array corresponding to x << 1 in binary.
|
|
||||||
func ShiftBytesLeft(x []byte) []byte {
|
|
||||||
l := len(x)
|
|
||||||
dst := make([]byte, l)
|
|
||||||
for i := 0; i < l-1; i++ {
|
|
||||||
dst[i] = (x[i] << 1) | (x[i+1] >> 7)
|
|
||||||
}
|
|
||||||
dst[l-1] = x[l-1] << 1
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShiftNBytesLeft puts in dst the byte array corresponding to x << n in binary.
|
|
||||||
func ShiftNBytesLeft(dst, x []byte, n int) {
|
|
||||||
// Erase first n / 8 bytes
|
|
||||||
copy(dst, x[n/8:])
|
|
||||||
|
|
||||||
// Shift the remaining n % 8 bits
|
|
||||||
bits := uint(n % 8)
|
|
||||||
l := len(dst)
|
|
||||||
for i := 0; i < l-1; i++ {
|
|
||||||
dst[i] = (dst[i] << bits) | (dst[i+1] >> uint(8-bits))
|
|
||||||
}
|
|
||||||
dst[l-1] = dst[l-1] << bits
|
|
||||||
|
|
||||||
// Append trailing zeroes
|
|
||||||
dst = append(dst, make([]byte, n/8)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// XorBytesMut assumes equal input length, replaces X with X XOR Y
|
|
||||||
func XorBytesMut(X, Y []byte) {
|
|
||||||
for i := 0; i < len(X); i++ {
|
|
||||||
X[i] ^= Y[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XorBytes assumes equal input length, puts X XOR Y into Z
|
|
||||||
func XorBytes(Z, X, Y []byte) {
|
|
||||||
for i := 0; i < len(X); i++ {
|
|
||||||
Z[i] = X[i] ^ Y[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RightXor XORs smaller input (assumed Y) at the right of the larger input (assumed X)
|
|
||||||
func RightXor(X, Y []byte) []byte {
|
|
||||||
offset := len(X) - len(Y)
|
|
||||||
xored := make([]byte, len(X))
|
|
||||||
copy(xored, X)
|
|
||||||
for i := 0; i < len(Y); i++ {
|
|
||||||
xored[offset+i] ^= Y[i]
|
|
||||||
}
|
|
||||||
return xored
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
@ -1,317 +0,0 @@
|
|||||||
// Copyright (C) 2019 ProtonTech AG
|
|
||||||
|
|
||||||
// Package ocb provides an implementation of the OCB (offset codebook) mode of
|
|
||||||
// operation, as described in RFC-7253 of the IRTF and in Rogaway, Bellare,
|
|
||||||
// Black and Krovetz - OCB: A BLOCK-CIPHER MODE OF OPERATION FOR EFFICIENT
|
|
||||||
// AUTHENTICATED ENCRYPTION (2003).
|
|
||||||
// Security considerations (from RFC-7253): A private key MUST NOT be used to
|
|
||||||
// encrypt more than 2^48 blocks. Tag length should be at least 12 bytes (a
|
|
||||||
// brute-force forging adversary succeeds after 2^{tag length} attempts). A
|
|
||||||
// single key SHOULD NOT be used to decrypt ciphertext with different tag
|
|
||||||
// lengths. Nonces need not be secret, but MUST NOT be reused.
|
|
||||||
// This package only supports underlying block ciphers with 128-bit blocks,
|
|
||||||
// such as AES-{128, 192, 256}, but may be extended to other sizes.
|
|
||||||
package ocb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/internal/byteutil"
|
|
||||||
"math/bits"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ocb struct {
|
|
||||||
block cipher.Block
|
|
||||||
tagSize int
|
|
||||||
nonceSize int
|
|
||||||
mask mask
|
|
||||||
// Optimized en/decrypt: For each nonce N used to en/decrypt, the 'Ktop'
|
|
||||||
// internal variable can be reused for en/decrypting with nonces sharing
|
|
||||||
// all but the last 6 bits with N. The prefix of the first nonce used to
|
|
||||||
// compute the new Ktop, and the Ktop value itself, are stored in
|
|
||||||
// reusableKtop. If using incremental nonces, this saves one block cipher
|
|
||||||
// call every 63 out of 64 OCB encryptions, and stores one nonce and one
|
|
||||||
// output of the block cipher in memory only.
|
|
||||||
reusableKtop reusableKtop
|
|
||||||
}
|
|
||||||
|
|
||||||
type mask struct {
|
|
||||||
// L_*, L_$, (L_i)_{i ∈ N}
|
|
||||||
lAst []byte
|
|
||||||
lDol []byte
|
|
||||||
L [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type reusableKtop struct {
|
|
||||||
noncePrefix []byte
|
|
||||||
Ktop []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultTagSize = 16
|
|
||||||
defaultNonceSize = 15
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
enc = iota
|
|
||||||
dec
|
|
||||||
)
|
|
||||||
|
|
||||||
func (o *ocb) NonceSize() int {
|
|
||||||
return o.nonceSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ocb) Overhead() int {
|
|
||||||
return o.tagSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOCB returns an OCB instance with the given block cipher and default
|
|
||||||
// tag and nonce sizes.
|
|
||||||
func NewOCB(block cipher.Block) (cipher.AEAD, error) {
|
|
||||||
return NewOCBWithNonceAndTagSize(block, defaultNonceSize, defaultTagSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOCBWithNonceAndTagSize returns an OCB instance with the given block
|
|
||||||
// cipher, nonce length, and tag length. Panics on zero nonceSize and
|
|
||||||
// exceedingly long tag size.
|
|
||||||
//
|
|
||||||
// It is recommended to use at least 12 bytes as tag length.
|
|
||||||
func NewOCBWithNonceAndTagSize(
|
|
||||||
block cipher.Block, nonceSize, tagSize int) (cipher.AEAD, error) {
|
|
||||||
if block.BlockSize() != 16 {
|
|
||||||
return nil, ocbError("Block cipher must have 128-bit blocks")
|
|
||||||
}
|
|
||||||
if nonceSize < 1 {
|
|
||||||
return nil, ocbError("Incorrect nonce length")
|
|
||||||
}
|
|
||||||
if nonceSize >= block.BlockSize() {
|
|
||||||
return nil, ocbError("Nonce length exceeds blocksize - 1")
|
|
||||||
}
|
|
||||||
if tagSize > block.BlockSize() {
|
|
||||||
return nil, ocbError("Custom tag length exceeds blocksize")
|
|
||||||
}
|
|
||||||
return &ocb{
|
|
||||||
block: block,
|
|
||||||
tagSize: tagSize,
|
|
||||||
nonceSize: nonceSize,
|
|
||||||
mask: initializeMaskTable(block),
|
|
||||||
reusableKtop: reusableKtop{
|
|
||||||
noncePrefix: nil,
|
|
||||||
Ktop: nil,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ocb) Seal(dst, nonce, plaintext, adata []byte) []byte {
|
|
||||||
if len(nonce) > o.nonceSize {
|
|
||||||
panic("crypto/ocb: Incorrect nonce length given to OCB")
|
|
||||||
}
|
|
||||||
ret, out := byteutil.SliceForAppend(dst, len(plaintext)+o.tagSize)
|
|
||||||
o.crypt(enc, out, nonce, adata, plaintext)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ocb) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
|
|
||||||
if len(nonce) > o.nonceSize {
|
|
||||||
panic("Nonce too long for this instance")
|
|
||||||
}
|
|
||||||
if len(ciphertext) < o.tagSize {
|
|
||||||
return nil, ocbError("Ciphertext shorter than tag length")
|
|
||||||
}
|
|
||||||
sep := len(ciphertext) - o.tagSize
|
|
||||||
ret, out := byteutil.SliceForAppend(dst, len(ciphertext))
|
|
||||||
ciphertextData := ciphertext[:sep]
|
|
||||||
tag := ciphertext[sep:]
|
|
||||||
o.crypt(dec, out, nonce, adata, ciphertextData)
|
|
||||||
if subtle.ConstantTimeCompare(ret[sep:], tag) == 1 {
|
|
||||||
ret = ret[:sep]
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
for i := range out {
|
|
||||||
out[i] = 0
|
|
||||||
}
|
|
||||||
return nil, ocbError("Tag authentication failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// On instruction enc (resp. dec), crypt is the encrypt (resp. decrypt)
|
|
||||||
// function. It returns the resulting plain/ciphertext with the tag appended.
|
|
||||||
func (o *ocb) crypt(instruction int, Y, nonce, adata, X []byte) []byte {
|
|
||||||
//
|
|
||||||
// Consider X as a sequence of 128-bit blocks
|
|
||||||
//
|
|
||||||
// Note: For encryption (resp. decryption), X is the plaintext (resp., the
|
|
||||||
// ciphertext without the tag).
|
|
||||||
blockSize := o.block.BlockSize()
|
|
||||||
|
|
||||||
//
|
|
||||||
// Nonce-dependent and per-encryption variables
|
|
||||||
//
|
|
||||||
// Zero out the last 6 bits of the nonce into truncatedNonce to see if Ktop
|
|
||||||
// is already computed.
|
|
||||||
truncatedNonce := make([]byte, len(nonce))
|
|
||||||
copy(truncatedNonce, nonce)
|
|
||||||
truncatedNonce[len(truncatedNonce)-1] &= 192
|
|
||||||
Ktop := make([]byte, blockSize)
|
|
||||||
if bytes.Equal(truncatedNonce, o.reusableKtop.noncePrefix) {
|
|
||||||
Ktop = o.reusableKtop.Ktop
|
|
||||||
} else {
|
|
||||||
// Nonce = num2str(TAGLEN mod 128, 7) || zeros(120 - bitlen(N)) || 1 || N
|
|
||||||
paddedNonce := append(make([]byte, blockSize-1-len(nonce)), 1)
|
|
||||||
paddedNonce = append(paddedNonce, truncatedNonce...)
|
|
||||||
paddedNonce[0] |= byte(((8 * o.tagSize) % (8 * blockSize)) << 1)
|
|
||||||
// Last 6 bits of paddedNonce are already zero. Encrypt into Ktop
|
|
||||||
paddedNonce[blockSize-1] &= 192
|
|
||||||
Ktop = paddedNonce
|
|
||||||
o.block.Encrypt(Ktop, Ktop)
|
|
||||||
o.reusableKtop.noncePrefix = truncatedNonce
|
|
||||||
o.reusableKtop.Ktop = Ktop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stretch = Ktop || ((lower half of Ktop) XOR (lower half of Ktop << 8))
|
|
||||||
xorHalves := make([]byte, blockSize/2)
|
|
||||||
byteutil.XorBytes(xorHalves, Ktop[:blockSize/2], Ktop[1:1+blockSize/2])
|
|
||||||
stretch := append(Ktop, xorHalves...)
|
|
||||||
bottom := int(nonce[len(nonce)-1] & 63)
|
|
||||||
offset := make([]byte, len(stretch))
|
|
||||||
byteutil.ShiftNBytesLeft(offset, stretch, bottom)
|
|
||||||
offset = offset[:blockSize]
|
|
||||||
|
|
||||||
//
|
|
||||||
// Process any whole blocks
|
|
||||||
//
|
|
||||||
// Note: For encryption Y is ciphertext || tag, for decryption Y is
|
|
||||||
// plaintext || tag.
|
|
||||||
checksum := make([]byte, blockSize)
|
|
||||||
m := len(X) / blockSize
|
|
||||||
for i := 0; i < m; i++ {
|
|
||||||
index := bits.TrailingZeros(uint(i + 1))
|
|
||||||
if len(o.mask.L)-1 < index {
|
|
||||||
o.mask.extendTable(index)
|
|
||||||
}
|
|
||||||
byteutil.XorBytesMut(offset, o.mask.L[bits.TrailingZeros(uint(i+1))])
|
|
||||||
blockX := X[i*blockSize : (i+1)*blockSize]
|
|
||||||
blockY := Y[i*blockSize : (i+1)*blockSize]
|
|
||||||
byteutil.XorBytes(blockY, blockX, offset)
|
|
||||||
switch instruction {
|
|
||||||
case enc:
|
|
||||||
o.block.Encrypt(blockY, blockY)
|
|
||||||
byteutil.XorBytesMut(blockY, offset)
|
|
||||||
byteutil.XorBytesMut(checksum, blockX)
|
|
||||||
case dec:
|
|
||||||
o.block.Decrypt(blockY, blockY)
|
|
||||||
byteutil.XorBytesMut(blockY, offset)
|
|
||||||
byteutil.XorBytesMut(checksum, blockY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// Process any final partial block and compute raw tag
|
|
||||||
//
|
|
||||||
tag := make([]byte, blockSize)
|
|
||||||
if len(X)%blockSize != 0 {
|
|
||||||
byteutil.XorBytesMut(offset, o.mask.lAst)
|
|
||||||
pad := make([]byte, blockSize)
|
|
||||||
o.block.Encrypt(pad, offset)
|
|
||||||
chunkX := X[blockSize*m:]
|
|
||||||
chunkY := Y[blockSize*m : len(X)]
|
|
||||||
byteutil.XorBytes(chunkY, chunkX, pad[:len(chunkX)])
|
|
||||||
// P_* || bit(1) || zeroes(127) - len(P_*)
|
|
||||||
switch instruction {
|
|
||||||
case enc:
|
|
||||||
paddedY := append(chunkX, byte(128))
|
|
||||||
paddedY = append(paddedY, make([]byte, blockSize-len(chunkX)-1)...)
|
|
||||||
byteutil.XorBytesMut(checksum, paddedY)
|
|
||||||
case dec:
|
|
||||||
paddedX := append(chunkY, byte(128))
|
|
||||||
paddedX = append(paddedX, make([]byte, blockSize-len(chunkY)-1)...)
|
|
||||||
byteutil.XorBytesMut(checksum, paddedX)
|
|
||||||
}
|
|
||||||
byteutil.XorBytes(tag, checksum, offset)
|
|
||||||
byteutil.XorBytesMut(tag, o.mask.lDol)
|
|
||||||
o.block.Encrypt(tag, tag)
|
|
||||||
byteutil.XorBytesMut(tag, o.hash(adata))
|
|
||||||
copy(Y[blockSize*m+len(chunkY):], tag[:o.tagSize])
|
|
||||||
} else {
|
|
||||||
byteutil.XorBytes(tag, checksum, offset)
|
|
||||||
byteutil.XorBytesMut(tag, o.mask.lDol)
|
|
||||||
o.block.Encrypt(tag, tag)
|
|
||||||
byteutil.XorBytesMut(tag, o.hash(adata))
|
|
||||||
copy(Y[blockSize*m:], tag[:o.tagSize])
|
|
||||||
}
|
|
||||||
return Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// This hash function is used to compute the tag. Per design, on empty input it
|
|
||||||
// returns a slice of zeros, of the same length as the underlying block cipher
|
|
||||||
// block size.
|
|
||||||
func (o *ocb) hash(adata []byte) []byte {
|
|
||||||
//
|
|
||||||
// Consider A as a sequence of 128-bit blocks
|
|
||||||
//
|
|
||||||
A := make([]byte, len(adata))
|
|
||||||
copy(A, adata)
|
|
||||||
blockSize := o.block.BlockSize()
|
|
||||||
|
|
||||||
//
|
|
||||||
// Process any whole blocks
|
|
||||||
//
|
|
||||||
sum := make([]byte, blockSize)
|
|
||||||
offset := make([]byte, blockSize)
|
|
||||||
m := len(A) / blockSize
|
|
||||||
for i := 0; i < m; i++ {
|
|
||||||
chunk := A[blockSize*i : blockSize*(i+1)]
|
|
||||||
index := bits.TrailingZeros(uint(i + 1))
|
|
||||||
// If the mask table is too short
|
|
||||||
if len(o.mask.L)-1 < index {
|
|
||||||
o.mask.extendTable(index)
|
|
||||||
}
|
|
||||||
byteutil.XorBytesMut(offset, o.mask.L[index])
|
|
||||||
byteutil.XorBytesMut(chunk, offset)
|
|
||||||
o.block.Encrypt(chunk, chunk)
|
|
||||||
byteutil.XorBytesMut(sum, chunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Process any final partial block; compute final hash value
|
|
||||||
//
|
|
||||||
if len(A)%blockSize != 0 {
|
|
||||||
byteutil.XorBytesMut(offset, o.mask.lAst)
|
|
||||||
// Pad block with 1 || 0 ^ 127 - bitlength(a)
|
|
||||||
ending := make([]byte, blockSize-len(A)%blockSize)
|
|
||||||
ending[0] = 0x80
|
|
||||||
encrypted := append(A[blockSize*m:], ending...)
|
|
||||||
byteutil.XorBytesMut(encrypted, offset)
|
|
||||||
o.block.Encrypt(encrypted, encrypted)
|
|
||||||
byteutil.XorBytesMut(sum, encrypted)
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
func initializeMaskTable(block cipher.Block) mask {
|
|
||||||
//
|
|
||||||
// Key-dependent variables
|
|
||||||
//
|
|
||||||
lAst := make([]byte, block.BlockSize())
|
|
||||||
block.Encrypt(lAst, lAst)
|
|
||||||
lDol := byteutil.GfnDouble(lAst)
|
|
||||||
L := make([][]byte, 1)
|
|
||||||
L[0] = byteutil.GfnDouble(lDol)
|
|
||||||
|
|
||||||
return mask{
|
|
||||||
lAst: lAst,
|
|
||||||
lDol: lDol,
|
|
||||||
L: L,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extends the L array of mask m up to L[limit], with L[i] = GfnDouble(L[i-1])
|
|
||||||
func (m *mask) extendTable(limit int) {
|
|
||||||
for i := len(m.L); i <= limit; i++ {
|
|
||||||
m.L = append(m.L, byteutil.GfnDouble(m.L[i-1]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ocbError(err string) error {
|
|
||||||
return errors.New("crypto/ocb: " + err)
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
// In the test vectors provided by RFC 7253, the "bottom"
|
|
||||||
// internal variable, which defines "offset" for the first time, does not
|
|
||||||
// exceed 15. However, it can attain values up to 63.
|
|
||||||
|
|
||||||
// These vectors include key length in {128, 192, 256}, tag size 128, and
|
|
||||||
// random nonce, header, and plaintext lengths.
|
|
||||||
|
|
||||||
// This file was automatically generated.
|
|
||||||
|
|
||||||
package ocb
|
|
||||||
|
|
||||||
var randomVectors = []struct {
|
|
||||||
key, nonce, header, plaintext, ciphertext string
|
|
||||||
}{
|
|
||||||
|
|
||||||
{"9438C5D599308EAF13F800D2D31EA7F0",
|
|
||||||
"C38EE4801BEBFFA1CD8635BE",
|
|
||||||
"0E507B7DADD8A98CDFE272D3CB6B3E8332B56AE583FB049C0874D4200BED16BD1A044182434E9DA0E841F182DFD5B3016B34641CED0784F1745F63AB3D0DA22D3351C9EF9A658B8081E24498EBF61FCE40DA6D8E184536",
|
|
||||||
"962D227786FB8913A8BAD5DC3250",
|
|
||||||
"EEDEF5FFA5986D1E3BF86DDD33EF9ADC79DCA06E215FA772CCBA814F63AD"},
|
|
||||||
{"BA7DE631C7D6712167C6724F5B9A2B1D",
|
|
||||||
"35263EBDA05765DC0E71F1F5",
|
|
||||||
"0103257B4224507C0242FEFE821EA7FA42E0A82863E5F8B68F7D881B4B44FA428A2B6B21D2F591260802D8AB6D83",
|
|
||||||
"9D6D1FC93AE8A64E7889B7B2E3521EFA9B920A8DDB692E6F833DDC4A38AFA535E5E2A3ED82CB7E26404AB86C54D01C4668F28398C2DF33D5D561CBA1C8DCFA7A912F5048E545B59483C0E3221F54B14DAA2E4EB657B3BEF9554F34CAD69B2724AE962D3D8A",
|
|
||||||
"E93852D1985C5E775655E937FA79CE5BF28A585F2AF53A5018853B9634BE3C84499AC0081918FDCE0624494D60E25F76ACD6853AC7576E3C350F332249BFCABD4E73CEABC36BE4EDDA40914E598AE74174A0D7442149B26990899491BDDFE8FC54D6C18E83AE9E9A6FFBF5D376565633862EEAD88D"},
|
|
||||||
{"2E74B25289F6FD3E578C24866E9C72A5",
|
|
||||||
"FD912F15025AF8414642BA1D1D",
|
|
||||||
"FB5FB8C26F365EEDAB5FE260C6E3CCD27806729C8335F146063A7F9EA93290E56CF84576EB446350D22AD730547C267B1F0BBB97EB34E1E2C41A",
|
|
||||||
"6C092EBF78F76EE8C1C6E592277D9545BA16EDB67BC7D8480B9827702DC2F8A129E2B08A2CE710CA7E1DA45CE162BB6CD4B512E632116E2211D3C90871EFB06B8D4B902681C7FB",
|
|
||||||
"6AC0A77F26531BF4F354A1737F99E49BE32ECD909A7A71AD69352906F54B08A9CE9B8CA5D724CBFFC5673437F23F630697F3B84117A1431D6FA8CC13A974FB4AD360300522E09511B99E71065D5AC4BBCB1D791E864EF4"},
|
|
||||||
{"E7EC507C802528F790AFF5303A017B17",
|
|
||||||
"4B97A7A568940A9E3CE7A99E93031E",
|
|
||||||
"28349BDC5A09390C480F9B8AA3EDEA3DDB8B9D64BCA322C570B8225DF0E31190DAB25A4014BA39519E02ABFB12B89AA28BBFD29E486E7FB28734258C817B63CED9912DBAFEBB93E2798AB2890DE3B0ACFCFF906AB15563EF7823CE83D27CDB251195E22BD1337BCBDE65E7C2C427321C463C2777BFE5AEAA",
|
|
||||||
"9455B3EA706B74",
|
|
||||||
"7F33BA3EA848D48A96B9530E26888F43EBD4463C9399B6"},
|
|
||||||
{"6C928AA3224736F28EE7378DE0090191",
|
|
||||||
"8936138E2E4C6A13280017A1622D",
|
|
||||||
"6202717F2631565BDCDC57C6584543E72A7C8BD444D0D108ED35069819633C",
|
|
||||||
"DA0691439E5F035F3E455269D14FE5C201C8C9B0A3FE2D3F86BCC59387C868FE65733D388360B31E3CE28B4BF6A8BE636706B536D5720DB66B47CF1C7A5AFD6F61E0EF90F1726D6B0E169F9A768B2B7AE4EE00A17F630AC905FCAAA1B707FFF25B3A1AAE83B504837C64A5639B2A34002B300EC035C9B43654DA55",
|
|
||||||
"B8804D182AB0F0EEB464FA7BD1329AD6154F982013F3765FEDFE09E26DAC078C9C1439BFC1159D6C02A25E3FF83EF852570117B315852AD5EE20E0FA3AA0A626B0E43BC0CEA38B44579DD36803455FB46989B90E6D229F513FD727AF8372517E9488384C515D6067704119C931299A0982EDDFB9C2E86A90C450C077EB222511EC9CCABC9FCFDB19F70088"},
|
|
||||||
{"ECEA315CA4B3F425B0C9957A17805EA4",
|
|
||||||
"664CDAE18403F4F9BA13015A44FC",
|
|
||||||
"642AFB090D6C6DB46783F08B01A3EF2A8FEB5736B531EAC226E7888FCC8505F396818F83105065FACB3267485B9E5E4A0261F621041C08FCCB2A809A49AB5252A91D0971BCC620B9D614BD77E57A0EED2FA5",
|
|
||||||
"6852C31F8083E20E364CEA21BB7854D67CEE812FE1C9ED2425C0932A90D3780728D1BB",
|
|
||||||
"2ECEF962A9695A463ADABB275BDA9FF8B2BA57AEC2F52EFFB700CD9271A74D2A011C24AEA946051BD6291776429B7E681BA33E"},
|
|
||||||
{"4EE616C4A58AAA380878F71A373461F6",
|
|
||||||
"91B8C9C176D9C385E9C47E52",
|
|
||||||
"CDA440B7F9762C572A718AC754EDEECC119E5EE0CCB9FEA4FFB22EEE75087C032EBF3DA9CDD8A28CC010B99ED45143B41A4BA50EA2A005473F89639237838867A57F23B0F0ED3BF22490E4501DAC9C658A9B9F",
|
|
||||||
"D6E645FA9AE410D15B8123FD757FA356A8DBE9258DDB5BE88832E615910993F497EC",
|
|
||||||
"B70ED7BF959FB2AAED4F36174A2A99BFB16992C8CDF369C782C4DB9C73DE78C5DB8E0615F647243B97ACDB24503BC9CADC48"},
|
|
||||||
{"DCD475773136C830D5E3D0C5FE05B7FF",
|
|
||||||
"BB8E1FBB483BE7616A922C4A",
|
|
||||||
"36FEF2E1CB29E76A6EA663FC3AF66ECD7404F466382F7B040AABED62293302B56E8783EF7EBC21B4A16C3E78A7483A0A403F253A2CDC5BBF79DC3DAE6C73F39A961D8FBBE8D41B",
|
|
||||||
"441E886EA38322B2437ECA7DEB5282518865A66780A454E510878E61BFEC3106A3CD93D2A02052E6F9E1832F9791053E3B76BF4C07EFDD6D4106E3027FABB752E60C1AA425416A87D53938163817A1051EBA1D1DEEB4B9B25C7E97368B52E5911A31810B0EC5AF547559B6142D9F4C4A6EF24A4CF75271BF9D48F62B",
|
|
||||||
"1BE4DD2F4E25A6512C2CC71D24BBB07368589A94C2714962CD0ACE5605688F06342587521E75F0ACAFFD86212FB5C34327D238DB36CF2B787794B9A4412E7CD1410EA5DDD2450C265F29CF96013CD213FD2880657694D718558964BC189B4A84AFCF47EB012935483052399DBA5B088B0A0477F20DFE0E85DCB735E21F22A439FB837DD365A93116D063E607"},
|
|
||||||
{"3FBA2B3D30177FFE15C1C59ED2148BB2C091F5615FBA7C07",
|
|
||||||
"FACF804A4BEBF998505FF9DE",
|
|
||||||
"8213B9263B2971A5BDA18DBD02208EE1",
|
|
||||||
"15B323926993B326EA19F892D704439FC478828322AF72118748284A1FD8A6D814E641F70512FD706980337379F31DC63355974738D7FEA87AD2858C0C2EBBFBE74371C21450072373C7B651B334D7C4D43260B9D7CCD3AF9EDB",
|
|
||||||
"6D35DC1469B26E6AAB26272A41B46916397C24C485B61162E640A062D9275BC33DDCFD3D9E1A53B6C8F51AC89B66A41D59B3574197A40D9B6DCF8A4E2A001409C8112F16B9C389E0096179DB914E05D6D11ED0005AD17E1CE105A2F0BAB8F6B1540DEB968B7A5428FF44"},
|
|
||||||
{"53B52B8D4D748BCDF1DDE68857832FA46227FA6E2F32EFA1",
|
|
||||||
"0B0EF53D4606B28D1398355F",
|
|
||||||
"F23882436349094AF98BCACA8218E81581A043B19009E28EFBF2DE37883E04864148CC01D240552CA8844EC1456F42034653067DA67E80F87105FD06E14FF771246C9612867BE4D215F6D761",
|
|
||||||
"F15030679BD4088D42CAC9BF2E9606EAD4798782FA3ED8C57EBE7F84A53236F51B25967C6489D0CD20C9EEA752F9BC",
|
|
||||||
"67B96E2D67C3729C96DAEAEDF821D61C17E648643A2134C5621FEC621186915AD80864BFD1EB5B238BF526A679385E012A457F583AFA78134242E9D9C1B4E4"},
|
|
||||||
{"0272DD80F23399F49BFC320381A5CD8225867245A49A7D41",
|
|
||||||
"5C83F4896D0738E1366B1836",
|
|
||||||
"69B0337289B19F73A12BAEEA857CCAF396C11113715D9500CCCF48BA08CFF12BC8B4BADB3084E63B85719DB5058FA7C2C11DEB096D7943CFA7CAF5",
|
|
||||||
"C01AD10FC8B562CD17C7BC2FAB3E26CBDFF8D7F4DEA816794BBCC12336991712972F52816AABAB244EB43B0137E2BAC1DD413CE79531E78BEF782E6B439612BB3AEF154DE3502784F287958EBC159419F9EBA27916A28D6307324129F506B1DE80C1755A929F87",
|
|
||||||
"FEFE52DD7159C8DD6E8EC2D3D3C0F37AB6CB471A75A071D17EC4ACDD8F3AA4D7D4F7BB559F3C09099E3D9003E5E8AA1F556B79CECDE66F85B08FA5955E6976BF2695EA076388A62D2AD5BAB7CBF1A7F3F4C8D5CDF37CDE99BD3E30B685D9E5EEE48C7C89118EF4878EB89747F28271FA2CC45F8E9E7601"},
|
|
||||||
{"3EEAED04A455D6E5E5AB53CFD5AFD2F2BC625C7BF4BE49A5",
|
|
||||||
"36B88F63ADBB5668588181D774",
|
|
||||||
"D367E3CB3703E762D23C6533188EF7028EFF9D935A3977150361997EC9DEAF1E4794BDE26AA8B53C124980B1362EC86FCDDFC7A90073171C1BAEE351A53234B86C66E8AB92FAE99EC6967A6D3428892D80",
|
|
||||||
"573454C719A9A55E04437BF7CBAAF27563CCCD92ADD5E515CD63305DFF0687E5EEF790C5DCA5C0033E9AB129505E2775438D92B38F08F3B0356BA142C6F694",
|
|
||||||
"E9F79A5B432D9E682C9AAA5661CFC2E49A0FCB81A431E54B42EB73DD3BED3F377FEC556ABA81624BA64A5D739AD41467460088F8D4F442180A9382CA635745473794C382FCDDC49BA4EB6D8A44AE3C"},
|
|
||||||
{"B695C691538F8CBD60F039D0E28894E3693CC7C36D92D79D",
|
|
||||||
"BC099AEB637361BAC536B57618",
|
|
||||||
"BFFF1A65AE38D1DC142C71637319F5F6508E2CB33C9DCB94202B359ED5A5ED8042E7F4F09231D32A7242976677E6F4C549BF65FADC99E5AF43F7A46FD95E16C2",
|
|
||||||
"081DF3FD85B415D803F0BE5AC58CFF0023FDDED99788296C3731D8",
|
|
||||||
"E50C64E3614D94FE69C47092E46ACC9957C6FEA2CCBF96BC62FBABE7424753C75F9C147C42AE26FE171531"},
|
|
||||||
{"C9ACBD2718F0689A1BE9802A551B6B8D9CF5614DAF5E65ED",
|
|
||||||
"B1B0AAF373B8B026EB80422051D8",
|
|
||||||
"6648C0E61AC733C76119D23FB24548D637751387AA2EAE9D80E912B7BD486CAAD9EAF4D7A5FE2B54AAD481E8EC94BB4D558000896E2010462B70C9FED1E7273080D1",
|
|
||||||
"189F591F6CB6D59AFEDD14C341741A8F1037DC0DF00FC57CE65C30F49E860255CEA5DC6019380CC0FE8880BC1A9E685F41C239C38F36E3F2A1388865C5C311059C0A",
|
|
||||||
"922A5E949B61D03BE34AB5F4E58607D4504EA14017BB363DAE3C873059EA7A1C77A746FB78981671D26C2CF6D9F24952D510044CE02A10177E9DB42D0145211DFE6E84369C5E3BC2669EAB4147B2822895F9"},
|
|
||||||
{"7A832BD2CF5BF4919F353CE2A8C86A5E406DA2D52BE16A72",
|
|
||||||
"2F2F17CECF7E5A756D10785A3CB9DB",
|
|
||||||
"61DA05E3788CC2D8405DBA70C7A28E5AF699863C9F72E6C6770126929F5D6FA267F005EBCF49495CB46400958A3AE80D1289D1C671",
|
|
||||||
"44E91121195A41AF14E8CFDBD39A4B517BE0DF1A72977ED8A3EEF8EEDA1166B2EB6DB2C4AE2E74FA0F0C74537F659BFBD141E5DDEC67E64EDA85AABD3F52C85A785B9FB3CECD70E7DF",
|
|
||||||
"BEDF596EA21288D2B84901E188F6EE1468B14D5161D3802DBFE00D60203A24E2AB62714BF272A45551489838C3A7FEAADC177B591836E73684867CCF4E12901DCF2064058726BBA554E84ADC5136F507E961188D4AF06943D3"},
|
|
||||||
{"1508E8AE9079AA15F1CEC4F776B4D11BCCB061B58AA56C18",
|
|
||||||
"BCA625674F41D1E3AB47672DC0C3",
|
|
||||||
"8B12CF84F16360F0EAD2A41BC021530FFCEC7F3579CAE658E10E2D3D81870F65AFCED0C77C6C4C6E6BA424FF23088C796BA6195ABA35094BF1829E089662E7A95FC90750AE16D0C8AFA55DAC789D7735B970B58D4BE7CEC7341DA82A0179A01929C27A59C5063215B859EA43",
|
|
||||||
"E525422519ECE070E82C",
|
|
||||||
"B47BC07C3ED1C0A43BA52C43CBACBCDBB29CAF1001E09FDF7107"},
|
|
||||||
{"7550C2761644E911FE9ADD119BAC07376BEA442845FEAD876D7E7AC1B713E464",
|
|
||||||
"36D2EC25ADD33CDEDF495205BBC923",
|
|
||||||
"7FCFE81A3790DE97FFC3DE160C470847EA7E841177C2F759571CBD837EA004A6CA8C6F4AEBFF2E9FD552D73EB8A30705D58D70C0B67AEEA280CBBF0A477358ACEF1E7508F2735CD9A0E4F9AC92B8C008F575D3B6278F1C18BD01227E3502E5255F3AB1893632AD00C717C588EF652A51A43209E7EE90",
|
|
||||||
"2B1A62F8FDFAA3C16470A21AD307C9A7D03ADE8EF72C69B06F8D738CDE578D7AEFD0D40BD9C022FB9F580DF5394C998ACCCEFC5471A3996FB8F1045A81FDC6F32D13502EA65A211390C8D882B8E0BEFD8DD8CBEF51D1597B124E9F7F",
|
|
||||||
"C873E02A22DB89EB0787DB6A60B99F7E4A0A085D5C4232A81ADCE2D60AA36F92DDC33F93DD8640AC0E08416B187FB382B3EC3EE85A64B0E6EE41C1366A5AD2A282F66605E87031CCBA2FA7B2DA201D975994AADE3DD1EE122AE09604AD489B84BF0C1AB7129EE16C6934850E"},
|
|
||||||
{"A51300285E554FDBDE7F771A9A9A80955639DD87129FAEF74987C91FB9687C71",
|
|
||||||
"81691D5D20EC818FCFF24B33DECC",
|
|
||||||
"C948093218AA9EB2A8E44A87EEA73FC8B6B75A196819A14BD83709EA323E8DF8B491045220E1D88729A38DBCFFB60D3056DAD4564498FD6574F74512945DEB34B69329ACED9FFC05D5D59DFCD5B973E2ACAFE6AD1EF8BBBC49351A2DD12508ED89ED",
|
|
||||||
"EB861165DAF7625F827C6B574ED703F03215",
|
|
||||||
"C6CD1CE76D2B3679C1B5AA1CFD67CCB55444B6BFD3E22C81CBC9BB738796B83E54E3"},
|
|
||||||
{"8CE0156D26FAEB7E0B9B800BBB2E9D4075B5EAC5C62358B0E7F6FCE610223282",
|
|
||||||
"D2A7B94DD12CDACA909D3AD7",
|
|
||||||
"E021A78F374FC271389AB9A3E97077D755",
|
|
||||||
"7C26000B58929F5095E1CEE154F76C2A299248E299F9B5ADE6C403AA1FD4A67FD4E0232F214CE7B919EE7A1027D2B76C57475715CD078461",
|
|
||||||
"C556FB38DF069B56F337B5FF5775CE6EAA16824DFA754F20B78819028EA635C3BB7AA731DE8776B2DCB67DCA2D33EEDF3C7E52EA450013722A41755A0752433ED17BDD5991AAE77A"},
|
|
||||||
{"1E8000A2CE00A561C9920A30BF0D7B983FEF8A1014C8F04C35CA6970E6BA02BD",
|
|
||||||
"65ED3D63F79F90BBFD19775E",
|
|
||||||
"336A8C0B7243582A46B221AA677647FCAE91",
|
|
||||||
"134A8B34824A290E7B",
|
|
||||||
"914FBEF80D0E6E17F8BDBB6097EBF5FBB0554952DC2B9E5151"},
|
|
||||||
{"53D5607BBE690B6E8D8F6D97F3DF2BA853B682597A214B8AA0EA6E598650AF15",
|
|
||||||
"C391A856B9FE234E14BA1AC7BB40FF",
|
|
||||||
"479682BC21349C4BE1641D5E78FE2C79EC1B9CF5470936DCAD9967A4DCD7C4EFADA593BC9EDE71E6A08829B8580901B61E274227E9D918502DE3",
|
|
||||||
"EAD154DC09C5E26C5D26FF33ED148B27120C7F2C23225CC0D0631B03E1F6C6D96FEB88C1A4052ACB4CE746B884B6502931F407021126C6AAB8C514C077A5A38438AE88EE",
|
|
||||||
"938821286EBB671D999B87C032E1D6055392EB564E57970D55E545FC5E8BAB90E6E3E3C0913F6320995FC636D72CD9919657CC38BD51552F4A502D8D1FE56DB33EBAC5092630E69EBB986F0E15CEE9FC8C052501"},
|
|
||||||
{"294362FCC984F440CEA3E9F7D2C06AF20C53AAC1B3738CA2186C914A6E193ABB",
|
|
||||||
"B15B61C8BB39261A8F55AB178EC3",
|
|
||||||
"D0729B6B75BB",
|
|
||||||
"2BD089ADCE9F334BAE3B065996C7D616DD0C27DF4218DCEEA0FBCA0F968837CE26B0876083327E25681FDDD620A32EC0DA12F73FAE826CC94BFF2B90A54D2651",
|
|
||||||
"AC94B25E4E21DE2437B806966CCD5D9385EF0CD4A51AB9FA6DE675C7B8952D67802E9FEC1FDE9F5D1EAB06057498BC0EEA454804FC9D2068982A3E24182D9AC2E7AB9994DDC899A604264583F63D066B"},
|
|
||||||
{"959DBFEB039B1A5B8CE6A44649B602AAA5F98A906DB96143D202CD2024F749D9",
|
|
||||||
"01D7BDB1133E9C347486C1EFA6",
|
|
||||||
"F3843955BD741F379DD750585EDC55E2CDA05CCBA8C1F4622AC2FE35214BC3A019B8BD12C4CC42D9213D1E1556941E8D8450830287FFB3B763A13722DD4140ED9846FB5FFF745D7B0B967D810A068222E10B259AF1D392035B0D83DC1498A6830B11B2418A840212599171E0258A1C203B05362978",
|
|
||||||
"A21811232C950FA8B12237C2EBD6A7CD2C3A155905E9E0C7C120",
|
|
||||||
"63C1CE397B22F1A03F1FA549B43178BC405B152D3C95E977426D519B3DFCA28498823240592B6EEE7A14"},
|
|
||||||
{"096AE499F5294173F34FF2B375F0E5D5AB79D0D03B33B1A74D7D576826345DF4",
|
|
||||||
"0C52B3D11D636E5910A4DD76D32C",
|
|
||||||
"229E9ECA3053789E937447BC719467075B6138A142DA528DA8F0CF8DDF022FD9AF8E74779BA3AC306609",
|
|
||||||
"8B7A00038783E8BAF6EDEAE0C4EAB48FC8FD501A588C7E4A4DB71E3604F2155A97687D3D2FFF8569261375A513CF4398CE0F87CA1658A1050F6EF6C4EA3E25",
|
|
||||||
"C20B6CF8D3C8241825FD90B2EDAC7593600646E579A8D8DAAE9E2E40C3835FE801B2BE4379131452BC5182C90307B176DFBE2049544222FE7783147B690774F6D9D7CEF52A91E61E298E9AA15464AC"},
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
package ocb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test vectors from https://tools.ietf.org/html/rfc7253. Note that key is
|
|
||||||
// shared across tests.
|
|
||||||
var testKey, _ = hex.DecodeString("000102030405060708090A0B0C0D0E0F")
|
|
||||||
|
|
||||||
var rfc7253testVectors = []struct {
|
|
||||||
nonce, header, plaintext, ciphertext string
|
|
||||||
}{
|
|
||||||
{"BBAA99887766554433221100",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"785407BFFFC8AD9EDCC5520AC9111EE6"},
|
|
||||||
{"BBAA99887766554433221101",
|
|
||||||
"0001020304050607",
|
|
||||||
"0001020304050607",
|
|
||||||
"6820B3657B6F615A5725BDA0D3B4EB3A257C9AF1F8F03009"},
|
|
||||||
{"BBAA99887766554433221102",
|
|
||||||
"0001020304050607",
|
|
||||||
"",
|
|
||||||
"81017F8203F081277152FADE694A0A00"},
|
|
||||||
{"BBAA99887766554433221103",
|
|
||||||
"",
|
|
||||||
"0001020304050607",
|
|
||||||
"45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9"},
|
|
||||||
{"BBAA99887766554433221104",
|
|
||||||
"000102030405060708090A0B0C0D0E0F",
|
|
||||||
"000102030405060708090A0B0C0D0E0F",
|
|
||||||
"571D535B60B277188BE5147170A9A22C3AD7A4FF3835B8C5701C1CCEC8FC3358"},
|
|
||||||
{"BBAA99887766554433221105",
|
|
||||||
"000102030405060708090A0B0C0D0E0F",
|
|
||||||
"",
|
|
||||||
"8CF761B6902EF764462AD86498CA6B97"},
|
|
||||||
{"BBAA99887766554433221106",
|
|
||||||
"",
|
|
||||||
"000102030405060708090A0B0C0D0E0F",
|
|
||||||
"5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D"},
|
|
||||||
{"BBAA99887766554433221107",
|
|
||||||
"000102030405060708090A0B0C0D0E0F1011121314151617",
|
|
||||||
"000102030405060708090A0B0C0D0E0F1011121314151617",
|
|
||||||
"1CA2207308C87C010756104D8840CE1952F09673A448A122C92C62241051F57356D7F3C90BB0E07F"},
|
|
||||||
{"BBAA99887766554433221108",
|
|
||||||
"000102030405060708090A0B0C0D0E0F1011121314151617",
|
|
||||||
"",
|
|
||||||
"6DC225A071FC1B9F7C69F93B0F1E10DE"},
|
|
||||||
{"BBAA99887766554433221109",
|
|
||||||
"",
|
|
||||||
"000102030405060708090A0B0C0D0E0F1011121314151617",
|
|
||||||
"221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF"},
|
|
||||||
{"BBAA9988776655443322110A",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
|
|
||||||
"BD6F6C496201C69296C11EFD138A467ABD3C707924B964DEAFFC40319AF5A48540FBBA186C5553C68AD9F592A79A4240"},
|
|
||||||
{"BBAA9988776655443322110B",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
|
|
||||||
"",
|
|
||||||
"FE80690BEE8A485D11F32965BC9D2A32"},
|
|
||||||
{"BBAA9988776655443322110C",
|
|
||||||
"",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
|
|
||||||
"2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973792EF46040C53F1432BCDFB5E1DDE3BC18A5F840B52E653444D5DF"},
|
|
||||||
{"BBAA9988776655443322110D",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
|
||||||
"D5CA91748410C1751FF8A2F618255B68A0A12E093FF454606E59F9C1D0DDC54B65E8628E568BAD7AED07BA06A4A69483A7035490C5769E60"},
|
|
||||||
{"BBAA9988776655443322110E",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
|
||||||
"",
|
|
||||||
"C5CD9D1850C141E358649994EE701B68"},
|
|
||||||
{"BBAA9988776655443322110F",
|
|
||||||
"",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
|
||||||
"4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E479AD363AC366B95A98CA5F3000B1479"},
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package ocb
|
|
||||||
|
|
||||||
// Second set of test vectors from https://tools.ietf.org/html/rfc7253
|
|
||||||
var rfc7253TestVectorTaglen96 = struct {
|
|
||||||
key, nonce, header, plaintext, ciphertext string
|
|
||||||
}{"0F0E0D0C0B0A09080706050403020100",
|
|
||||||
"BBAA9988776655443322110D",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
|
||||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
|
||||||
"1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA"}
|
|
||||||
|
|
||||||
var rfc7253AlgorithmTest = []struct {
|
|
||||||
KEYLEN, TAGLEN int
|
|
||||||
OUTPUT string
|
|
||||||
}{
|
|
||||||
{128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"},
|
|
||||||
{192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"},
|
|
||||||
{256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"},
|
|
||||||
{128, 96, "77A3D8E73589158D25D01209"},
|
|
||||||
{192, 96, "05D56EAD2752C86BE6932C5E"},
|
|
||||||
{256, 96, "5458359AC23B0CBA9E6330DD"},
|
|
||||||
{128, 64, "192C9B7BD90BA06A"},
|
|
||||||
{192, 64, "0066BC6E0EF34E24"},
|
|
||||||
{256, 64, "7D4EA5D445501CBE"},
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
// Copyright 2014 Matthew Endsley
|
|
||||||
// All rights reserved
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted providing that the following conditions
|
|
||||||
// are met:
|
|
||||||
// 1. Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// 2. 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.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
|
||||||
|
|
||||||
// Package keywrap is an implementation of the RFC 3394 AES key wrapping
|
|
||||||
// algorithm. This is used in OpenPGP with elliptic curve keys.
|
|
||||||
package keywrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrWrapPlaintext is returned if the plaintext is not a multiple
|
|
||||||
// of 64 bits.
|
|
||||||
ErrWrapPlaintext = errors.New("keywrap: plainText must be a multiple of 64 bits")
|
|
||||||
|
|
||||||
// ErrUnwrapCiphertext is returned if the ciphertext is not a
|
|
||||||
// multiple of 64 bits.
|
|
||||||
ErrUnwrapCiphertext = errors.New("keywrap: cipherText must by a multiple of 64 bits")
|
|
||||||
|
|
||||||
// ErrUnwrapFailed is returned if unwrapping a key fails.
|
|
||||||
ErrUnwrapFailed = errors.New("keywrap: failed to unwrap key")
|
|
||||||
|
|
||||||
// NB: the AES NewCipher call only fails if the key is an invalid length.
|
|
||||||
|
|
||||||
// ErrInvalidKey is returned when the AES key is invalid.
|
|
||||||
ErrInvalidKey = errors.New("keywrap: invalid AES key")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wrap a key using the RFC 3394 AES Key Wrap Algorithm.
|
|
||||||
func Wrap(key, plainText []byte) ([]byte, error) {
|
|
||||||
if len(plainText)%8 != 0 {
|
|
||||||
return nil, ErrWrapPlaintext
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrInvalidKey
|
|
||||||
}
|
|
||||||
|
|
||||||
nblocks := len(plainText) / 8
|
|
||||||
|
|
||||||
// 1) Initialize variables.
|
|
||||||
var block [aes.BlockSize]byte
|
|
||||||
// - Set A = IV, an initial value (see 2.2.3)
|
|
||||||
for ii := 0; ii < 8; ii++ {
|
|
||||||
block[ii] = 0xA6
|
|
||||||
}
|
|
||||||
|
|
||||||
// - For i = 1 to n
|
|
||||||
// - Set R[i] = P[i]
|
|
||||||
intermediate := make([]byte, len(plainText))
|
|
||||||
copy(intermediate, plainText)
|
|
||||||
|
|
||||||
// 2) Calculate intermediate values.
|
|
||||||
for ii := 0; ii < 6; ii++ {
|
|
||||||
for jj := 0; jj < nblocks; jj++ {
|
|
||||||
// - B = AES(K, A | R[i])
|
|
||||||
copy(block[8:], intermediate[jj*8:jj*8+8])
|
|
||||||
c.Encrypt(block[:], block[:])
|
|
||||||
|
|
||||||
// - A = MSB(64, B) ^ t where t = (n*j)+1
|
|
||||||
t := uint64(ii*nblocks + jj + 1)
|
|
||||||
val := binary.BigEndian.Uint64(block[:8]) ^ t
|
|
||||||
binary.BigEndian.PutUint64(block[:8], val)
|
|
||||||
|
|
||||||
// - R[i] = LSB(64, B)
|
|
||||||
copy(intermediate[jj*8:jj*8+8], block[8:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Output results.
|
|
||||||
// - Set C[0] = A
|
|
||||||
// - For i = 1 to n
|
|
||||||
// - C[i] = R[i]
|
|
||||||
return append(block[:8], intermediate...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap a key using the RFC 3394 AES Key Wrap Algorithm.
|
|
||||||
func Unwrap(key, cipherText []byte) ([]byte, error) {
|
|
||||||
if len(cipherText)%8 != 0 {
|
|
||||||
return nil, ErrUnwrapCiphertext
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrInvalidKey
|
|
||||||
}
|
|
||||||
|
|
||||||
nblocks := len(cipherText)/8 - 1
|
|
||||||
|
|
||||||
// 1) Initialize variables.
|
|
||||||
var block [aes.BlockSize]byte
|
|
||||||
// - Set A = C[0]
|
|
||||||
copy(block[:8], cipherText[:8])
|
|
||||||
|
|
||||||
// - For i = 1 to n
|
|
||||||
// - Set R[i] = C[i]
|
|
||||||
intermediate := make([]byte, len(cipherText)-8)
|
|
||||||
copy(intermediate, cipherText[8:])
|
|
||||||
|
|
||||||
// 2) Compute intermediate values.
|
|
||||||
for jj := 5; jj >= 0; jj-- {
|
|
||||||
for ii := nblocks - 1; ii >= 0; ii-- {
|
|
||||||
// - B = AES-1(K, (A ^ t) | R[i]) where t = n*j+1
|
|
||||||
// - A = MSB(64, B)
|
|
||||||
t := uint64(jj*nblocks + ii + 1)
|
|
||||||
val := binary.BigEndian.Uint64(block[:8]) ^ t
|
|
||||||
binary.BigEndian.PutUint64(block[:8], val)
|
|
||||||
|
|
||||||
copy(block[8:], intermediate[ii*8:ii*8+8])
|
|
||||||
c.Decrypt(block[:], block[:])
|
|
||||||
|
|
||||||
// - R[i] = LSB(B, 64)
|
|
||||||
copy(intermediate[ii*8:ii*8+8], block[8:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Output results.
|
|
||||||
// - If A is an appropriate initial value (see 2.2.3),
|
|
||||||
for ii := 0; ii < 8; ii++ {
|
|
||||||
if block[ii] != 0xA6 {
|
|
||||||
return nil, ErrUnwrapFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - For i = 1 to n
|
|
||||||
// - P[i] = R[i]
|
|
||||||
return intermediate, nil
|
|
||||||
}
|
|
@ -1,231 +0,0 @@
|
|||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
|
|
||||||
// very similar to PEM except that it has an additional CRC checksum.
|
|
||||||
package armor // import "github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Block represents an OpenPGP armored structure.
|
|
||||||
//
|
|
||||||
// The encoded form is:
|
|
||||||
//
|
|
||||||
// -----BEGIN Type-----
|
|
||||||
// Headers
|
|
||||||
//
|
|
||||||
// base64-encoded Bytes
|
|
||||||
// '=' base64 encoded checksum
|
|
||||||
// -----END Type-----
|
|
||||||
//
|
|
||||||
// where Headers is a possibly empty sequence of Key: Value lines.
|
|
||||||
//
|
|
||||||
// Since the armored data can be very large, this package presents a streaming
|
|
||||||
// interface.
|
|
||||||
type Block struct {
|
|
||||||
Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE").
|
|
||||||
Header map[string]string // Optional headers.
|
|
||||||
Body io.Reader // A Reader from which the contents can be read
|
|
||||||
lReader lineReader
|
|
||||||
oReader openpgpReader
|
|
||||||
}
|
|
||||||
|
|
||||||
var ArmorCorrupt error = errors.StructuralError("armor invalid")
|
|
||||||
|
|
||||||
const crc24Init = 0xb704ce
|
|
||||||
const crc24Poly = 0x1864cfb
|
|
||||||
const crc24Mask = 0xffffff
|
|
||||||
|
|
||||||
// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
|
|
||||||
func crc24(crc uint32, d []byte) uint32 {
|
|
||||||
for _, b := range d {
|
|
||||||
crc ^= uint32(b) << 16
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
crc <<= 1
|
|
||||||
if crc&0x1000000 != 0 {
|
|
||||||
crc ^= crc24Poly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc
|
|
||||||
}
|
|
||||||
|
|
||||||
var armorStart = []byte("-----BEGIN ")
|
|
||||||
var armorEnd = []byte("-----END ")
|
|
||||||
var armorEndOfLine = []byte("-----")
|
|
||||||
|
|
||||||
// lineReader wraps a line based reader. It watches for the end of an armor
|
|
||||||
// block and records the expected CRC value.
|
|
||||||
type lineReader struct {
|
|
||||||
in *bufio.Reader
|
|
||||||
buf []byte
|
|
||||||
eof bool
|
|
||||||
crc uint32
|
|
||||||
crcSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lineReader) Read(p []byte) (n int, err error) {
|
|
||||||
if l.eof {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(l.buf) > 0 {
|
|
||||||
n = copy(p, l.buf)
|
|
||||||
l.buf = l.buf[n:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line, isPrefix, err := l.in.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isPrefix {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.HasPrefix(line, armorEnd) {
|
|
||||||
l.eof = true
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) == 5 && line[0] == '=' {
|
|
||||||
// This is the checksum line
|
|
||||||
var expectedBytes [3]byte
|
|
||||||
var m int
|
|
||||||
m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
|
|
||||||
if m != 3 || err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.crc = uint32(expectedBytes[0])<<16 |
|
|
||||||
uint32(expectedBytes[1])<<8 |
|
|
||||||
uint32(expectedBytes[2])
|
|
||||||
|
|
||||||
line, _, err = l.in.ReadLine()
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !bytes.HasPrefix(line, armorEnd) {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
l.eof = true
|
|
||||||
l.crcSet = true
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) > 96 {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy(p, line)
|
|
||||||
bytesToSave := len(line) - n
|
|
||||||
if bytesToSave > 0 {
|
|
||||||
if cap(l.buf) < bytesToSave {
|
|
||||||
l.buf = make([]byte, 0, bytesToSave)
|
|
||||||
}
|
|
||||||
l.buf = l.buf[0:bytesToSave]
|
|
||||||
copy(l.buf, line[n:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// openpgpReader passes Read calls to the underlying base64 decoder, but keeps
|
|
||||||
// a running CRC of the resulting data and checks the CRC against the value
|
|
||||||
// found by the lineReader at EOF.
|
|
||||||
type openpgpReader struct {
|
|
||||||
lReader *lineReader
|
|
||||||
b64Reader io.Reader
|
|
||||||
currentCRC uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *openpgpReader) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = r.b64Reader.Read(p)
|
|
||||||
r.currentCRC = crc24(r.currentCRC, p[:n])
|
|
||||||
|
|
||||||
if err == io.EOF && r.lReader.crcSet && r.lReader.crc != uint32(r.currentCRC&crc24Mask) {
|
|
||||||
return 0, ArmorCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode reads a PGP armored block from the given Reader. It will ignore
|
|
||||||
// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
|
|
||||||
// given Reader is not usable after calling this function: an arbitrary amount
|
|
||||||
// of data may have been read past the end of the block.
|
|
||||||
func Decode(in io.Reader) (p *Block, err error) {
|
|
||||||
r := bufio.NewReaderSize(in, 100)
|
|
||||||
var line []byte
|
|
||||||
ignoreNext := false
|
|
||||||
|
|
||||||
TryNextBlock:
|
|
||||||
p = nil
|
|
||||||
|
|
||||||
// Skip leading garbage
|
|
||||||
for {
|
|
||||||
ignoreThis := ignoreNext
|
|
||||||
line, ignoreNext, err = r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ignoreNext || ignoreThis {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = bytes.TrimSpace(line)
|
|
||||||
if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p = new(Block)
|
|
||||||
p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
|
|
||||||
p.Header = make(map[string]string)
|
|
||||||
nextIsContinuation := false
|
|
||||||
var lastKey string
|
|
||||||
|
|
||||||
// Read headers
|
|
||||||
for {
|
|
||||||
isContinuation := nextIsContinuation
|
|
||||||
line, nextIsContinuation, err = r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
p = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isContinuation {
|
|
||||||
p.Header[lastKey] += string(line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = bytes.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
i := bytes.Index(line, []byte(":"))
|
|
||||||
if i == -1 {
|
|
||||||
goto TryNextBlock
|
|
||||||
}
|
|
||||||
lastKey = string(line[:i])
|
|
||||||
var value string
|
|
||||||
if len(line) > i+2 {
|
|
||||||
value = string(line[i+2:])
|
|
||||||
}
|
|
||||||
p.Header[lastKey] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
p.lReader.in = r
|
|
||||||
p.oReader.currentCRC = crc24Init
|
|
||||||
p.oReader.lReader = &p.lReader
|
|
||||||
p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
|
|
||||||
p.Body = &p.oReader
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package armor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var armorHeaderSep = []byte(": ")
|
|
||||||
var blockEnd = []byte("\n=")
|
|
||||||
var newline = []byte("\n")
|
|
||||||
var armorEndOfLineOut = []byte("-----\n")
|
|
||||||
|
|
||||||
// writeSlices writes its arguments to the given Writer.
|
|
||||||
func writeSlices(out io.Writer, slices ...[]byte) (err error) {
|
|
||||||
for _, s := range slices {
|
|
||||||
_, err = out.Write(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// lineBreaker breaks data across several lines, all of the same byte length
|
|
||||||
// (except possibly the last). Lines are broken with a single '\n'.
|
|
||||||
type lineBreaker struct {
|
|
||||||
lineLength int
|
|
||||||
line []byte
|
|
||||||
used int
|
|
||||||
out io.Writer
|
|
||||||
haveWritten bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
|
|
||||||
return &lineBreaker{
|
|
||||||
lineLength: lineLength,
|
|
||||||
line: make([]byte, lineLength),
|
|
||||||
used: 0,
|
|
||||||
out: out,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lineBreaker) Write(b []byte) (n int, err error) {
|
|
||||||
n = len(b)
|
|
||||||
|
|
||||||
if n == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.used == 0 && l.haveWritten {
|
|
||||||
_, err = l.out.Write([]byte{'\n'})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.used+len(b) < l.lineLength {
|
|
||||||
l.used += copy(l.line[l.used:], b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.haveWritten = true
|
|
||||||
_, err = l.out.Write(l.line[0:l.used])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
excess := l.lineLength - l.used
|
|
||||||
l.used = 0
|
|
||||||
|
|
||||||
_, err = l.out.Write(b[0:excess])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = l.Write(b[excess:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lineBreaker) Close() (err error) {
|
|
||||||
if l.used > 0 {
|
|
||||||
_, err = l.out.Write(l.line[0:l.used])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// encoding keeps track of a running CRC24 over the data which has been written
|
|
||||||
// to it and outputs a OpenPGP checksum when closed, followed by an armor
|
|
||||||
// trailer.
|
|
||||||
//
|
|
||||||
// It's built into a stack of io.Writers:
|
|
||||||
//
|
|
||||||
// encoding -> base64 encoder -> lineBreaker -> out
|
|
||||||
type encoding struct {
|
|
||||||
out io.Writer
|
|
||||||
breaker *lineBreaker
|
|
||||||
b64 io.WriteCloser
|
|
||||||
crc uint32
|
|
||||||
blockType []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encoding) Write(data []byte) (n int, err error) {
|
|
||||||
e.crc = crc24(e.crc, data)
|
|
||||||
return e.b64.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encoding) Close() (err error) {
|
|
||||||
err = e.b64.Close()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.breaker.Close()
|
|
||||||
|
|
||||||
var checksumBytes [3]byte
|
|
||||||
checksumBytes[0] = byte(e.crc >> 16)
|
|
||||||
checksumBytes[1] = byte(e.crc >> 8)
|
|
||||||
checksumBytes[2] = byte(e.crc)
|
|
||||||
|
|
||||||
var b64ChecksumBytes [4]byte
|
|
||||||
base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
|
|
||||||
|
|
||||||
return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns a WriteCloser which will encode the data written to it in
|
|
||||||
// OpenPGP armor.
|
|
||||||
func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
|
|
||||||
bType := []byte(blockType)
|
|
||||||
err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range headers {
|
|
||||||
err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = out.Write(newline)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e := &encoding{
|
|
||||||
out: out,
|
|
||||||
breaker: newLineBreaker(out, 64),
|
|
||||||
crc: crc24Init,
|
|
||||||
blockType: bType,
|
|
||||||
}
|
|
||||||
e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
|
|
||||||
return e, nil
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package openpgp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewCanonicalTextHash reformats text written to it into the canonical
|
|
||||||
// form and then applies the hash h. See RFC 4880, section 5.2.1.
|
|
||||||
func NewCanonicalTextHash(h hash.Hash) hash.Hash {
|
|
||||||
return &canonicalTextHash{h, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
type canonicalTextHash struct {
|
|
||||||
h hash.Hash
|
|
||||||
s int
|
|
||||||
}
|
|
||||||
|
|
||||||
var newline = []byte{'\r', '\n'}
|
|
||||||
|
|
||||||
func writeCanonical(cw io.Writer, buf []byte, s *int) (int, error) {
|
|
||||||
start := 0
|
|
||||||
for i, c := range buf {
|
|
||||||
switch *s {
|
|
||||||
case 0:
|
|
||||||
if c == '\r' {
|
|
||||||
*s = 1
|
|
||||||
} else if c == '\n' {
|
|
||||||
cw.Write(buf[start:i])
|
|
||||||
cw.Write(newline)
|
|
||||||
start = i + 1
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
*s = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cw.Write(buf[start:])
|
|
||||||
return len(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Write(buf []byte) (int, error) {
|
|
||||||
return writeCanonical(cth.h, buf, &cth.s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Sum(in []byte) []byte {
|
|
||||||
return cth.h.Sum(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Reset() {
|
|
||||||
cth.h.Reset()
|
|
||||||
cth.s = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) Size() int {
|
|
||||||
return cth.h.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cth *canonicalTextHash) BlockSize() int {
|
|
||||||
return cth.h.BlockSize()
|
|
||||||
}
|
|
@ -1,464 +0,0 @@
|
|||||||
// 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 clearsign generates and processes OpenPGP, clear-signed data. See
|
|
||||||
// RFC 4880, section 7.
|
|
||||||
//
|
|
||||||
// Clearsigned messages are cryptographically signed, but the contents of the
|
|
||||||
// message are kept in plaintext so that it can be read without special tools.
|
|
||||||
package clearsign // import "github.com/ProtonMail/go-crypto/openpgp/clearsign"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"net/textproto"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Block represents a clearsigned message. A signature on a Block can
|
|
||||||
// be checked by calling Block.VerifySignature.
|
|
||||||
type Block struct {
|
|
||||||
Headers textproto.MIMEHeader // Optional unverified Hash headers
|
|
||||||
Plaintext []byte // The original message text
|
|
||||||
Bytes []byte // The signed message
|
|
||||||
ArmoredSignature *armor.Block // The signature block
|
|
||||||
}
|
|
||||||
|
|
||||||
// start is the marker which denotes the beginning of a clearsigned message.
|
|
||||||
var start = []byte("\n-----BEGIN PGP SIGNED MESSAGE-----")
|
|
||||||
|
|
||||||
// dashEscape is prefixed to any lines that begin with a hyphen so that they
|
|
||||||
// can't be confused with endText.
|
|
||||||
var dashEscape = []byte("- ")
|
|
||||||
|
|
||||||
// endText is a marker which denotes the end of the message and the start of
|
|
||||||
// an armored signature.
|
|
||||||
var endText = []byte("-----BEGIN PGP SIGNATURE-----")
|
|
||||||
|
|
||||||
// end is a marker which denotes the end of the armored signature.
|
|
||||||
var end = []byte("\n-----END PGP SIGNATURE-----")
|
|
||||||
|
|
||||||
var crlf = []byte("\r\n")
|
|
||||||
var lf = byte('\n')
|
|
||||||
|
|
||||||
// getLine returns the first \r\n or \n delineated line from the given byte
|
|
||||||
// array. The line does not include the \r\n or \n. The remainder of the byte
|
|
||||||
// array (also not including the new line bytes) is also returned and this will
|
|
||||||
// always be smaller than the original argument.
|
|
||||||
func getLine(data []byte) (line, rest []byte) {
|
|
||||||
i := bytes.Index(data, []byte{'\n'})
|
|
||||||
var j int
|
|
||||||
if i < 0 {
|
|
||||||
i = len(data)
|
|
||||||
j = i
|
|
||||||
} else {
|
|
||||||
j = i + 1
|
|
||||||
if i > 0 && data[i-1] == '\r' {
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data[0:i], data[j:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode finds the first clearsigned message in data and returns it, as well as
|
|
||||||
// the suffix of data which remains after the message. Any prefix data is
|
|
||||||
// discarded.
|
|
||||||
//
|
|
||||||
// If no message is found, or if the message is invalid, Decode returns nil and
|
|
||||||
// the whole data slice. The only allowed header type is Hash, and it is not
|
|
||||||
// verified against the signature hash.
|
|
||||||
func Decode(data []byte) (b *Block, rest []byte) {
|
|
||||||
// start begins with a newline. However, at the very beginning of
|
|
||||||
// the byte array, we'll accept the start string without it.
|
|
||||||
rest = data
|
|
||||||
if bytes.HasPrefix(data, start[1:]) {
|
|
||||||
rest = rest[len(start)-1:]
|
|
||||||
} else if i := bytes.Index(data, start); i >= 0 {
|
|
||||||
rest = rest[i+len(start):]
|
|
||||||
} else {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume the start line and check it does not have a suffix.
|
|
||||||
suffix, rest := getLine(rest)
|
|
||||||
if len(suffix) != 0 {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
|
|
||||||
var line []byte
|
|
||||||
b = &Block{
|
|
||||||
Headers: make(textproto.MIMEHeader),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next come a series of header lines.
|
|
||||||
for {
|
|
||||||
// This loop terminates because getLine's second result is
|
|
||||||
// always smaller than its argument.
|
|
||||||
if len(rest) == 0 {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
// An empty line marks the end of the headers.
|
|
||||||
if line, rest = getLine(rest); len(line) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject headers with control or Unicode characters.
|
|
||||||
if i := bytes.IndexFunc(line, func(r rune) bool {
|
|
||||||
return r < 0x20 || r > 0x7e
|
|
||||||
}); i != -1 {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
|
|
||||||
i := bytes.Index(line, []byte{':'})
|
|
||||||
if i == -1 {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
|
|
||||||
key, val := string(line[0:i]), string(line[i+1:])
|
|
||||||
key = strings.TrimSpace(key)
|
|
||||||
if key != "Hash" {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
for _, val := range strings.Split(val, ",") {
|
|
||||||
val = strings.TrimSpace(val)
|
|
||||||
b.Headers.Add(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
firstLine := true
|
|
||||||
for {
|
|
||||||
start := rest
|
|
||||||
|
|
||||||
line, rest = getLine(rest)
|
|
||||||
if len(line) == 0 && len(rest) == 0 {
|
|
||||||
// No armored data was found, so this isn't a complete message.
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
if bytes.Equal(line, endText) {
|
|
||||||
// Back up to the start of the line because armor expects to see the
|
|
||||||
// header line.
|
|
||||||
rest = start
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// The final CRLF isn't included in the hash so we don't write it until
|
|
||||||
// we've seen the next line.
|
|
||||||
if firstLine {
|
|
||||||
firstLine = false
|
|
||||||
} else {
|
|
||||||
b.Bytes = append(b.Bytes, crlf...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.HasPrefix(line, dashEscape) {
|
|
||||||
line = line[2:]
|
|
||||||
}
|
|
||||||
line = bytes.TrimRight(line, " \t")
|
|
||||||
b.Bytes = append(b.Bytes, line...)
|
|
||||||
|
|
||||||
b.Plaintext = append(b.Plaintext, line...)
|
|
||||||
b.Plaintext = append(b.Plaintext, lf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to find the extent of the armored data (including any newlines at
|
|
||||||
// the end).
|
|
||||||
i := bytes.Index(rest, end)
|
|
||||||
if i == -1 {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
i += len(end)
|
|
||||||
for i < len(rest) && (rest[i] == '\r' || rest[i] == '\n') {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
armored := rest[:i]
|
|
||||||
rest = rest[i:]
|
|
||||||
|
|
||||||
var err error
|
|
||||||
b.ArmoredSignature, err = armor.Decode(bytes.NewBuffer(armored))
|
|
||||||
if err != nil {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, rest
|
|
||||||
}
|
|
||||||
|
|
||||||
// A dashEscaper is an io.WriteCloser which processes the body of a clear-signed
|
|
||||||
// message. The clear-signed message is written to buffered and a hash, suitable
|
|
||||||
// for signing, is maintained in h.
|
|
||||||
//
|
|
||||||
// When closed, an armored signature is created and written to complete the
|
|
||||||
// message.
|
|
||||||
type dashEscaper struct {
|
|
||||||
buffered *bufio.Writer
|
|
||||||
hashers []hash.Hash // one per key in privateKeys
|
|
||||||
hashType crypto.Hash
|
|
||||||
toHash io.Writer // writes to all the hashes in hashers
|
|
||||||
|
|
||||||
atBeginningOfLine bool
|
|
||||||
isFirstLine bool
|
|
||||||
|
|
||||||
whitespace []byte
|
|
||||||
byteBuf []byte // a one byte buffer to save allocations
|
|
||||||
|
|
||||||
privateKeys []*packet.PrivateKey
|
|
||||||
config *packet.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dashEscaper) Write(data []byte) (n int, err error) {
|
|
||||||
for _, b := range data {
|
|
||||||
d.byteBuf[0] = b
|
|
||||||
|
|
||||||
if d.atBeginningOfLine {
|
|
||||||
// The final CRLF isn't included in the hash so we have to wait
|
|
||||||
// until this point (the start of the next line) before writing it.
|
|
||||||
if !d.isFirstLine {
|
|
||||||
d.toHash.Write(crlf)
|
|
||||||
}
|
|
||||||
d.isFirstLine = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any whitespace at the end of the line has to be removed so we
|
|
||||||
// buffer it until we find out whether there's more on this line.
|
|
||||||
if b == ' ' || b == '\t' || b == '\r' {
|
|
||||||
d.whitespace = append(d.whitespace, b)
|
|
||||||
d.atBeginningOfLine = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.atBeginningOfLine {
|
|
||||||
// At the beginning of a line, hyphens have to be escaped.
|
|
||||||
if b == '-' {
|
|
||||||
// The signature isn't calculated over the dash-escaped text so
|
|
||||||
// the escape is only written to buffered.
|
|
||||||
if _, err = d.buffered.Write(dashEscape); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.toHash.Write(d.byteBuf)
|
|
||||||
d.atBeginningOfLine = false
|
|
||||||
} else if b == '\n' {
|
|
||||||
// Nothing to do because we delay writing CRLF to the hash.
|
|
||||||
} else {
|
|
||||||
d.toHash.Write(d.byteBuf)
|
|
||||||
d.atBeginningOfLine = false
|
|
||||||
}
|
|
||||||
if err = d.buffered.WriteByte(b); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if b == '\n' {
|
|
||||||
// We got a raw \n. Drop any trailing whitespace and write a
|
|
||||||
// CRLF.
|
|
||||||
d.whitespace = d.whitespace[:0]
|
|
||||||
// We delay writing CRLF to the hash until the start of the
|
|
||||||
// next line.
|
|
||||||
if err = d.buffered.WriteByte(b); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.atBeginningOfLine = true
|
|
||||||
} else {
|
|
||||||
// Any buffered whitespace wasn't at the end of the line so
|
|
||||||
// we need to write it out.
|
|
||||||
if len(d.whitespace) > 0 {
|
|
||||||
d.toHash.Write(d.whitespace)
|
|
||||||
if _, err = d.buffered.Write(d.whitespace); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.whitespace = d.whitespace[:0]
|
|
||||||
}
|
|
||||||
d.toHash.Write(d.byteBuf)
|
|
||||||
if err = d.buffered.WriteByte(b); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n = len(data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dashEscaper) Close() (err error) {
|
|
||||||
if !d.atBeginningOfLine {
|
|
||||||
if err = d.buffered.WriteByte(lf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t := d.config.Now()
|
|
||||||
for i, k := range d.privateKeys {
|
|
||||||
sig := new(packet.Signature)
|
|
||||||
sig.SigType = packet.SigTypeText
|
|
||||||
sig.PubKeyAlgo = k.PubKeyAlgo
|
|
||||||
sig.Hash = d.hashType
|
|
||||||
sig.CreationTime = t
|
|
||||||
sig.IssuerKeyId = &k.KeyId
|
|
||||||
sig.IssuerFingerprint = k.Fingerprint
|
|
||||||
sig.Notations = d.config.Notations()
|
|
||||||
sigLifetimeSecs := d.config.SigLifetime()
|
|
||||||
sig.SigLifetimeSecs = &sigLifetimeSecs
|
|
||||||
|
|
||||||
if err = sig.Sign(d.hashers[i], k, d.config); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = sig.Serialize(out); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = out.Close(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = d.buffered.Flush(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns a WriteCloser which will clear-sign a message with privateKey
|
|
||||||
// and write it to w. If config is nil, sensible defaults are used.
|
|
||||||
func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeMulti returns a WriteCloser which will clear-sign a message with all the
|
|
||||||
// private keys indicated and write it to w. If config is nil, sensible defaults
|
|
||||||
// are used.
|
|
||||||
func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
for _, k := range privateKeys {
|
|
||||||
if k.Encrypted {
|
|
||||||
return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hashType := config.Hash()
|
|
||||||
name := nameOfHash(hashType)
|
|
||||||
if len(name) == 0 {
|
|
||||||
return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hashType.Available() {
|
|
||||||
return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
|
|
||||||
}
|
|
||||||
var hashers []hash.Hash
|
|
||||||
var ws []io.Writer
|
|
||||||
for range privateKeys {
|
|
||||||
h := hashType.New()
|
|
||||||
hashers = append(hashers, h)
|
|
||||||
ws = append(ws, h)
|
|
||||||
}
|
|
||||||
toHash := io.MultiWriter(ws...)
|
|
||||||
|
|
||||||
buffered := bufio.NewWriter(w)
|
|
||||||
// start has a \n at the beginning that we don't want here.
|
|
||||||
if _, err = buffered.Write(start[1:]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = buffered.WriteByte(lf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = buffered.WriteString("Hash: "); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = buffered.WriteString(name); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = buffered.WriteByte(lf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = buffered.WriteByte(lf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
plaintext = &dashEscaper{
|
|
||||||
buffered: buffered,
|
|
||||||
hashers: hashers,
|
|
||||||
hashType: hashType,
|
|
||||||
toHash: toHash,
|
|
||||||
|
|
||||||
atBeginningOfLine: true,
|
|
||||||
isFirstLine: true,
|
|
||||||
|
|
||||||
byteBuf: make([]byte, 1),
|
|
||||||
|
|
||||||
privateKeys: privateKeys,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySignature checks a clearsigned message signature, and checks that the
|
|
||||||
// hash algorithm in the header matches the hash algorithm in the signature.
|
|
||||||
func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) (signer *openpgp.Entity, err error) {
|
|
||||||
var expectedHashes []crypto.Hash
|
|
||||||
for _, v := range b.Headers {
|
|
||||||
for _, name := range v {
|
|
||||||
expectedHash := nameToHash(name)
|
|
||||||
if uint8(expectedHash) == 0 {
|
|
||||||
return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers")
|
|
||||||
}
|
|
||||||
expectedHashes = append(expectedHashes, expectedHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(expectedHashes) == 0 {
|
|
||||||
expectedHashes = append(expectedHashes, crypto.MD5)
|
|
||||||
}
|
|
||||||
return openpgp.CheckDetachedSignatureAndHash(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, expectedHashes, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nameOfHash returns the OpenPGP name for the given hash, or the empty string
|
|
||||||
// if the name isn't known. See RFC 4880, section 9.4.
|
|
||||||
func nameOfHash(h crypto.Hash) string {
|
|
||||||
switch h {
|
|
||||||
case crypto.SHA224:
|
|
||||||
return "SHA224"
|
|
||||||
case crypto.SHA256:
|
|
||||||
return "SHA256"
|
|
||||||
case crypto.SHA384:
|
|
||||||
return "SHA384"
|
|
||||||
case crypto.SHA512:
|
|
||||||
return "SHA512"
|
|
||||||
case crypto.SHA3_256:
|
|
||||||
return "SHA3-256"
|
|
||||||
case crypto.SHA3_512:
|
|
||||||
return "SHA3-512"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// nameToHash returns a hash for a given OpenPGP name, or 0
|
|
||||||
// if the name isn't known. See RFC 4880, section 9.4.
|
|
||||||
func nameToHash(h string) crypto.Hash {
|
|
||||||
switch h {
|
|
||||||
case "SHA1":
|
|
||||||
return crypto.SHA1
|
|
||||||
case "SHA224":
|
|
||||||
return crypto.SHA224
|
|
||||||
case "SHA256":
|
|
||||||
return crypto.SHA256
|
|
||||||
case "SHA384":
|
|
||||||
return crypto.SHA384
|
|
||||||
case "SHA512":
|
|
||||||
return crypto.SHA512
|
|
||||||
case "SHA3-256":
|
|
||||||
return crypto.SHA3_256
|
|
||||||
case "SHA3-512":
|
|
||||||
return crypto.SHA3_512
|
|
||||||
}
|
|
||||||
return crypto.Hash(0)
|
|
||||||
}
|
|
@ -1,210 +0,0 @@
|
|||||||
// Copyright 2017 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 ecdh implements ECDH encryption, suitable for OpenPGP,
|
|
||||||
// as specified in RFC 6637, section 8.
|
|
||||||
package ecdh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/aes/keywrap"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type KDF struct {
|
|
||||||
Hash algorithm.Hash
|
|
||||||
Cipher algorithm.Cipher
|
|
||||||
}
|
|
||||||
|
|
||||||
type PublicKey struct {
|
|
||||||
curve ecc.ECDHCurve
|
|
||||||
Point []byte
|
|
||||||
KDF
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivateKey struct {
|
|
||||||
PublicKey
|
|
||||||
D []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPublicKey(curve ecc.ECDHCurve, kdfHash algorithm.Hash, kdfCipher algorithm.Cipher) *PublicKey {
|
|
||||||
return &PublicKey{
|
|
||||||
curve: curve,
|
|
||||||
KDF: KDF{
|
|
||||||
Hash: kdfHash,
|
|
||||||
Cipher: kdfCipher,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrivateKey(key PublicKey) *PrivateKey {
|
|
||||||
return &PrivateKey{
|
|
||||||
PublicKey: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) GetCurve() ecc.ECDHCurve {
|
|
||||||
return pk.curve
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) MarshalPoint() []byte {
|
|
||||||
return pk.curve.MarshalBytePoint(pk.Point)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) UnmarshalPoint(p []byte) error {
|
|
||||||
pk.Point = pk.curve.UnmarshalBytePoint(p)
|
|
||||||
if pk.Point == nil {
|
|
||||||
return errors.New("ecdh: failed to parse EC point")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sk *PrivateKey) MarshalByteSecret() []byte {
|
|
||||||
return sk.curve.MarshalByteSecret(sk.D)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sk *PrivateKey) UnmarshalByteSecret(d []byte) error {
|
|
||||||
sk.D = sk.curve.UnmarshalByteSecret(d)
|
|
||||||
|
|
||||||
if sk.D == nil {
|
|
||||||
return errors.New("ecdh: failed to parse scalar")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateKey(rand io.Reader, c ecc.ECDHCurve, kdf KDF) (priv *PrivateKey, err error) {
|
|
||||||
priv = new(PrivateKey)
|
|
||||||
priv.PublicKey.curve = c
|
|
||||||
priv.PublicKey.KDF = kdf
|
|
||||||
priv.PublicKey.Point, priv.D, err = c.GenerateECDH(rand)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Encrypt(random io.Reader, pub *PublicKey, msg, curveOID, fingerprint []byte) (vsG, c []byte, err error) {
|
|
||||||
if len(msg) > 40 {
|
|
||||||
return nil, nil, errors.New("ecdh: message too long")
|
|
||||||
}
|
|
||||||
// the sender MAY use 21, 13, and 5 bytes of padding for AES-128,
|
|
||||||
// AES-192, and AES-256, respectively, to provide the same number of
|
|
||||||
// octets, 40 total, as an input to the key wrapping method.
|
|
||||||
padding := make([]byte, 40-len(msg))
|
|
||||||
for i := range padding {
|
|
||||||
padding[i] = byte(40 - len(msg))
|
|
||||||
}
|
|
||||||
m := append(msg, padding...)
|
|
||||||
|
|
||||||
ephemeral, zb, err := pub.curve.Encaps(random, pub.Point)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vsG = pub.curve.MarshalBytePoint(ephemeral)
|
|
||||||
|
|
||||||
z, err := buildKey(pub, zb, curveOID, fingerprint, false, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c, err = keywrap.Wrap(z, m); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return vsG, c, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func Decrypt(priv *PrivateKey, vsG, c, curveOID, fingerprint []byte) (msg []byte, err error) {
|
|
||||||
var m []byte
|
|
||||||
zb, err := priv.PublicKey.curve.Decaps(priv.curve.UnmarshalBytePoint(vsG), priv.D)
|
|
||||||
|
|
||||||
// Try buildKey three times to workaround an old bug, see comments in buildKey.
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
var z []byte
|
|
||||||
// RFC6637 §8: "Compute Z = KDF( S, Z_len, Param );"
|
|
||||||
z, err = buildKey(&priv.PublicKey, zb, curveOID, fingerprint, i == 1, i == 2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC6637 §8: "Compute C = AESKeyWrap( Z, c ) as per [RFC3394]"
|
|
||||||
m, err = keywrap.Unwrap(z, c)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only return an error after we've tried all (required) variants of buildKey.
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC6637 §8: "m = symm_alg_ID || session key || checksum || pkcs5_padding"
|
|
||||||
// The last byte should be the length of the padding, as per PKCS5; strip it off.
|
|
||||||
return m[:len(m)-int(m[len(m)-1])], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildKey(pub *PublicKey, zb []byte, curveOID, fingerprint []byte, stripLeading, stripTrailing bool) ([]byte, error) {
|
|
||||||
// Param = curve_OID_len || curve_OID || public_key_alg_ID || 03
|
|
||||||
// || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap
|
|
||||||
// || "Anonymous Sender " || recipient_fingerprint;
|
|
||||||
param := new(bytes.Buffer)
|
|
||||||
if _, err := param.Write(curveOID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
algKDF := []byte{18, 3, 1, pub.KDF.Hash.Id(), pub.KDF.Cipher.Id()}
|
|
||||||
if _, err := param.Write(algKDF); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := param.Write([]byte("Anonymous Sender ")); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// For v5 keys, the 20 leftmost octets of the fingerprint are used.
|
|
||||||
if _, err := param.Write(fingerprint[:20]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if param.Len()-len(curveOID) != 45 {
|
|
||||||
return nil, errors.New("ecdh: malformed KDF Param")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param );
|
|
||||||
h := pub.KDF.Hash.New()
|
|
||||||
if _, err := h.Write([]byte{0x0, 0x0, 0x0, 0x1}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
zbLen := len(zb)
|
|
||||||
i := 0
|
|
||||||
j := zbLen - 1
|
|
||||||
if stripLeading {
|
|
||||||
// Work around old go crypto bug where the leading zeros are missing.
|
|
||||||
for i < zbLen && zb[i] == 0 {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stripTrailing {
|
|
||||||
// Work around old OpenPGP.js bug where insignificant trailing zeros in
|
|
||||||
// this little-endian number are missing.
|
|
||||||
// (See https://github.com/openpgpjs/openpgpjs/pull/853.)
|
|
||||||
for j >= 0 && zb[j] == 0 {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := h.Write(zb[i : j+1]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := h.Write(param.Bytes()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mb := h.Sum(nil)
|
|
||||||
|
|
||||||
return mb[:pub.KDF.Cipher.KeySize()], nil // return oBits leftmost bits of MB.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func Validate(priv *PrivateKey) error {
|
|
||||||
return priv.curve.ValidateECDH(priv.Point, priv.D)
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
// Package ecdsa implements ECDSA signature, suitable for OpenPGP,
|
|
||||||
// as specified in RFC 6637, section 5.
|
|
||||||
package ecdsa
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PublicKey struct {
|
|
||||||
X, Y *big.Int
|
|
||||||
curve ecc.ECDSACurve
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivateKey struct {
|
|
||||||
PublicKey
|
|
||||||
D *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPublicKey(curve ecc.ECDSACurve) *PublicKey {
|
|
||||||
return &PublicKey{
|
|
||||||
curve: curve,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrivateKey(key PublicKey) *PrivateKey {
|
|
||||||
return &PrivateKey{
|
|
||||||
PublicKey: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) GetCurve() ecc.ECDSACurve {
|
|
||||||
return pk.curve
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) MarshalPoint() []byte {
|
|
||||||
return pk.curve.MarshalIntegerPoint(pk.X, pk.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) UnmarshalPoint(p []byte) error {
|
|
||||||
pk.X, pk.Y = pk.curve.UnmarshalIntegerPoint(p)
|
|
||||||
if pk.X == nil {
|
|
||||||
return errors.New("ecdsa: failed to parse EC point")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sk *PrivateKey) MarshalIntegerSecret() []byte {
|
|
||||||
return sk.curve.MarshalIntegerSecret(sk.D)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sk *PrivateKey) UnmarshalIntegerSecret(d []byte) error {
|
|
||||||
sk.D = sk.curve.UnmarshalIntegerSecret(d)
|
|
||||||
|
|
||||||
if sk.D == nil {
|
|
||||||
return errors.New("ecdsa: failed to parse scalar")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateKey(rand io.Reader, c ecc.ECDSACurve) (priv *PrivateKey, err error) {
|
|
||||||
priv = new(PrivateKey)
|
|
||||||
priv.PublicKey.curve = c
|
|
||||||
priv.PublicKey.X, priv.PublicKey.Y, priv.D, err = c.GenerateECDSA(rand)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
|
|
||||||
return priv.PublicKey.curve.Sign(rand, priv.X, priv.Y, priv.D, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
|
|
||||||
return pub.curve.Verify(pub.X, pub.Y, hash, r, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Validate(priv *PrivateKey) error {
|
|
||||||
return priv.curve.ValidateECDSA(priv.X, priv.Y, priv.D.Bytes())
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
// Package eddsa implements EdDSA signature, suitable for OpenPGP, as specified in
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7
|
|
||||||
package eddsa
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PublicKey struct {
|
|
||||||
X []byte
|
|
||||||
curve ecc.EdDSACurve
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivateKey struct {
|
|
||||||
PublicKey
|
|
||||||
D []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPublicKey(curve ecc.EdDSACurve) *PublicKey {
|
|
||||||
return &PublicKey{
|
|
||||||
curve: curve,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrivateKey(key PublicKey) *PrivateKey {
|
|
||||||
return &PrivateKey{
|
|
||||||
PublicKey: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) GetCurve() ecc.EdDSACurve {
|
|
||||||
return pk.curve
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) MarshalPoint() []byte {
|
|
||||||
return pk.curve.MarshalBytePoint(pk.X)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) UnmarshalPoint(x []byte) error {
|
|
||||||
pk.X = pk.curve.UnmarshalBytePoint(x)
|
|
||||||
|
|
||||||
if pk.X == nil {
|
|
||||||
return errors.New("eddsa: failed to parse EC point")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sk *PrivateKey) MarshalByteSecret() []byte {
|
|
||||||
return sk.curve.MarshalByteSecret(sk.D)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sk *PrivateKey) UnmarshalByteSecret(d []byte) error {
|
|
||||||
sk.D = sk.curve.UnmarshalByteSecret(d)
|
|
||||||
|
|
||||||
if sk.D == nil {
|
|
||||||
return errors.New("eddsa: failed to parse scalar")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateKey(rand io.Reader, c ecc.EdDSACurve) (priv *PrivateKey, err error) {
|
|
||||||
priv = new(PrivateKey)
|
|
||||||
priv.PublicKey.curve = c
|
|
||||||
priv.PublicKey.X, priv.D, err = c.GenerateEdDSA(rand)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sign(priv *PrivateKey, message []byte) (r, s []byte, err error) {
|
|
||||||
sig, err := priv.PublicKey.curve.Sign(priv.PublicKey.X, priv.D, message)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, s = priv.PublicKey.curve.MarshalSignature(sig)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Verify(pub *PublicKey, message, r, s []byte) bool {
|
|
||||||
sig := pub.curve.UnmarshalSignature(r, s)
|
|
||||||
if sig == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return pub.curve.Verify(pub.X, message, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Validate(priv *PrivateKey) error {
|
|
||||||
return priv.curve.ValidateEdDSA(priv.PublicKey.X, priv.D)
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package elgamal implements ElGamal encryption, suitable for OpenPGP,
|
|
||||||
// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on
|
|
||||||
// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31,
|
|
||||||
// n. 4, 1985, pp. 469-472.
|
|
||||||
//
|
|
||||||
// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it
|
|
||||||
// unsuitable for other protocols. RSA should be used in preference in any
|
|
||||||
// case.
|
|
||||||
package elgamal // import "github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKey represents an ElGamal public key.
|
|
||||||
type PublicKey struct {
|
|
||||||
G, P, Y *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateKey represents an ElGamal private key.
|
|
||||||
type PrivateKey struct {
|
|
||||||
PublicKey
|
|
||||||
X *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt encrypts the given message to the given public key. The result is a
|
|
||||||
// pair of integers. Errors can result from reading random, or because msg is
|
|
||||||
// too large to be encrypted to the public key.
|
|
||||||
func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) {
|
|
||||||
pLen := (pub.P.BitLen() + 7) / 8
|
|
||||||
if len(msg) > pLen-11 {
|
|
||||||
err = errors.New("elgamal: message too long")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// EM = 0x02 || PS || 0x00 || M
|
|
||||||
em := make([]byte, pLen-1)
|
|
||||||
em[0] = 2
|
|
||||||
ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):]
|
|
||||||
err = nonZeroRandomBytes(ps, random)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
em[len(em)-len(msg)-1] = 0
|
|
||||||
copy(mm, msg)
|
|
||||||
|
|
||||||
m := new(big.Int).SetBytes(em)
|
|
||||||
|
|
||||||
k, err := rand.Int(random, pub.P)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c1 = new(big.Int).Exp(pub.G, k, pub.P)
|
|
||||||
s := new(big.Int).Exp(pub.Y, k, pub.P)
|
|
||||||
c2 = s.Mul(s, m)
|
|
||||||
c2.Mod(c2, pub.P)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt takes two integers, resulting from an ElGamal encryption, and
|
|
||||||
// returns the plaintext of the message. An error can result only if the
|
|
||||||
// ciphertext is invalid. Users should keep in mind that this is a padding
|
|
||||||
// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can
|
|
||||||
// be used to break the cryptosystem. See “Chosen Ciphertext Attacks
|
|
||||||
// Against Protocols Based on the RSA Encryption Standard PKCS #1”, Daniel
|
|
||||||
// Bleichenbacher, Advances in Cryptology (Crypto '98),
|
|
||||||
func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
|
|
||||||
s := new(big.Int).Exp(c1, priv.X, priv.P)
|
|
||||||
if s.ModInverse(s, priv.P) == nil {
|
|
||||||
return nil, errors.New("elgamal: invalid private key")
|
|
||||||
}
|
|
||||||
s.Mul(s, c2)
|
|
||||||
s.Mod(s, priv.P)
|
|
||||||
em := s.Bytes()
|
|
||||||
|
|
||||||
firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2)
|
|
||||||
|
|
||||||
// The remainder of the plaintext must be a string of non-zero random
|
|
||||||
// octets, followed by a 0, followed by the message.
|
|
||||||
// lookingForIndex: 1 iff we are still looking for the zero.
|
|
||||||
// index: the offset of the first zero byte.
|
|
||||||
var lookingForIndex, index int
|
|
||||||
lookingForIndex = 1
|
|
||||||
|
|
||||||
for i := 1; i < len(em); i++ {
|
|
||||||
equals0 := subtle.ConstantTimeByteEq(em[i], 0)
|
|
||||||
index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index)
|
|
||||||
lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 {
|
|
||||||
return nil, errors.New("elgamal: decryption error")
|
|
||||||
}
|
|
||||||
return em[index+1:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nonZeroRandomBytes fills the given slice with non-zero random octets.
|
|
||||||
func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) {
|
|
||||||
_, err = io.ReadFull(rand, s)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
for s[i] == 0 {
|
|
||||||
_, err = io.ReadFull(rand, s[i:i+1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package errors contains common error types for the OpenPGP packages.
|
|
||||||
package errors // import "github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A StructuralError is returned when OpenPGP data is found to be syntactically
|
|
||||||
// invalid.
|
|
||||||
type StructuralError string
|
|
||||||
|
|
||||||
func (s StructuralError) Error() string {
|
|
||||||
return "openpgp: invalid data: " + string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnsupportedError indicates that, although the OpenPGP data is valid, it
|
|
||||||
// makes use of currently unimplemented features.
|
|
||||||
type UnsupportedError string
|
|
||||||
|
|
||||||
func (s UnsupportedError) Error() string {
|
|
||||||
return "openpgp: unsupported feature: " + string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidArgumentError indicates that the caller is in error and passed an
|
|
||||||
// incorrect value.
|
|
||||||
type InvalidArgumentError string
|
|
||||||
|
|
||||||
func (i InvalidArgumentError) Error() string {
|
|
||||||
return "openpgp: invalid argument: " + string(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignatureError indicates that a syntactically valid signature failed to
|
|
||||||
// validate.
|
|
||||||
type SignatureError string
|
|
||||||
|
|
||||||
func (b SignatureError) Error() string {
|
|
||||||
return "openpgp: invalid signature: " + string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrMDCHashMismatch error = SignatureError("MDC hash mismatch")
|
|
||||||
var ErrMDCMissing error = SignatureError("MDC packet not found")
|
|
||||||
|
|
||||||
type signatureExpiredError int
|
|
||||||
|
|
||||||
func (se signatureExpiredError) Error() string {
|
|
||||||
return "openpgp: signature expired"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrSignatureExpired error = signatureExpiredError(0)
|
|
||||||
|
|
||||||
type keyExpiredError int
|
|
||||||
|
|
||||||
func (ke keyExpiredError) Error() string {
|
|
||||||
return "openpgp: key expired"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrKeyExpired error = keyExpiredError(0)
|
|
||||||
|
|
||||||
type keyIncorrectError int
|
|
||||||
|
|
||||||
func (ki keyIncorrectError) Error() string {
|
|
||||||
return "openpgp: incorrect key"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrKeyIncorrect error = keyIncorrectError(0)
|
|
||||||
|
|
||||||
// KeyInvalidError indicates that the public key parameters are invalid
|
|
||||||
// as they do not match the private ones
|
|
||||||
type KeyInvalidError string
|
|
||||||
|
|
||||||
func (e KeyInvalidError) Error() string {
|
|
||||||
return "openpgp: invalid key: " + string(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
type unknownIssuerError int
|
|
||||||
|
|
||||||
func (unknownIssuerError) Error() string {
|
|
||||||
return "openpgp: signature made by unknown entity"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrUnknownIssuer error = unknownIssuerError(0)
|
|
||||||
|
|
||||||
type keyRevokedError int
|
|
||||||
|
|
||||||
func (keyRevokedError) Error() string {
|
|
||||||
return "openpgp: signature made by revoked key"
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrKeyRevoked error = keyRevokedError(0)
|
|
||||||
|
|
||||||
type UnknownPacketTypeError uint8
|
|
||||||
|
|
||||||
func (upte UnknownPacketTypeError) Error() string {
|
|
||||||
return "openpgp: unknown packet type: " + strconv.Itoa(int(upte))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AEADError indicates that there is a problem when initializing or using a
|
|
||||||
// AEAD instance, configuration struct, nonces or index values.
|
|
||||||
type AEADError string
|
|
||||||
|
|
||||||
func (ae AEADError) Error() string {
|
|
||||||
return "openpgp: aead error: " + string(ae)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrDummyPrivateKey results when operations are attempted on a private key
|
|
||||||
// that is just a dummy key. See
|
|
||||||
// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=fe55ae16ab4e26d8356dc574c9e8bc935e71aef1;hb=23191d7851eae2217ecdac6484349849a24fd94a#l1109
|
|
||||||
type ErrDummyPrivateKey string
|
|
||||||
|
|
||||||
func (dke ErrDummyPrivateKey) Error() string {
|
|
||||||
return "openpgp: s2k GNU dummy key: " + string(dke)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package openpgp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
|
|
||||||
// hash id.
|
|
||||||
func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
|
|
||||||
return algorithm.HashIdToHash(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashIdToString returns the name of the hash function corresponding to the
|
|
||||||
// given OpenPGP hash id.
|
|
||||||
func HashIdToString(id byte) (name string, ok bool) {
|
|
||||||
return algorithm.HashIdToString(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashToHashId returns an OpenPGP hash id which corresponds the given Hash.
|
|
||||||
func HashToHashId(h crypto.Hash) (id byte, ok bool) {
|
|
||||||
return algorithm.HashToHashId(h)
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
// Copyright (C) 2019 ProtonTech AG
|
|
||||||
|
|
||||||
package algorithm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"github.com/ProtonMail/go-crypto/eax"
|
|
||||||
"github.com/ProtonMail/go-crypto/ocb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AEADMode defines the Authenticated Encryption with Associated Data mode of
|
|
||||||
// operation.
|
|
||||||
type AEADMode uint8
|
|
||||||
|
|
||||||
// Supported modes of operation (see RFC4880bis [EAX] and RFC7253)
|
|
||||||
const (
|
|
||||||
AEADModeEAX = AEADMode(1)
|
|
||||||
AEADModeOCB = AEADMode(2)
|
|
||||||
AEADModeGCM = AEADMode(3)
|
|
||||||
)
|
|
||||||
|
|
||||||
// TagLength returns the length in bytes of authentication tags.
|
|
||||||
func (mode AEADMode) TagLength() int {
|
|
||||||
switch mode {
|
|
||||||
case AEADModeEAX:
|
|
||||||
return 16
|
|
||||||
case AEADModeOCB:
|
|
||||||
return 16
|
|
||||||
case AEADModeGCM:
|
|
||||||
return 16
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonceLength returns the length in bytes of nonces.
|
|
||||||
func (mode AEADMode) NonceLength() int {
|
|
||||||
switch mode {
|
|
||||||
case AEADModeEAX:
|
|
||||||
return 16
|
|
||||||
case AEADModeOCB:
|
|
||||||
return 15
|
|
||||||
case AEADModeGCM:
|
|
||||||
return 12
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a fresh instance of the given mode
|
|
||||||
func (mode AEADMode) New(block cipher.Block) (alg cipher.AEAD) {
|
|
||||||
var err error
|
|
||||||
switch mode {
|
|
||||||
case AEADModeEAX:
|
|
||||||
alg, err = eax.NewEAX(block)
|
|
||||||
case AEADModeOCB:
|
|
||||||
alg, err = ocb.NewOCB(block)
|
|
||||||
case AEADModeGCM:
|
|
||||||
alg, err = cipher.NewGCM(block)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return alg
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
// Copyright 2017 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 algorithm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/des"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cast5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cipher is an official symmetric key cipher algorithm. See RFC 4880,
|
|
||||||
// section 9.2.
|
|
||||||
type Cipher interface {
|
|
||||||
// Id returns the algorithm ID, as a byte, of the cipher.
|
|
||||||
Id() uint8
|
|
||||||
// KeySize returns the key size, in bytes, of the cipher.
|
|
||||||
KeySize() int
|
|
||||||
// BlockSize returns the block size, in bytes, of the cipher.
|
|
||||||
BlockSize() int
|
|
||||||
// New returns a fresh instance of the given cipher.
|
|
||||||
New(key []byte) cipher.Block
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following constants mirror the OpenPGP standard (RFC 4880).
|
|
||||||
const (
|
|
||||||
TripleDES = CipherFunction(2)
|
|
||||||
CAST5 = CipherFunction(3)
|
|
||||||
AES128 = CipherFunction(7)
|
|
||||||
AES192 = CipherFunction(8)
|
|
||||||
AES256 = CipherFunction(9)
|
|
||||||
)
|
|
||||||
|
|
||||||
// CipherById represents the different block ciphers specified for OpenPGP. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13
|
|
||||||
var CipherById = map[uint8]Cipher{
|
|
||||||
TripleDES.Id(): TripleDES,
|
|
||||||
CAST5.Id(): CAST5,
|
|
||||||
AES128.Id(): AES128,
|
|
||||||
AES192.Id(): AES192,
|
|
||||||
AES256.Id(): AES256,
|
|
||||||
}
|
|
||||||
|
|
||||||
type CipherFunction uint8
|
|
||||||
|
|
||||||
// ID returns the algorithm Id, as a byte, of cipher.
|
|
||||||
func (sk CipherFunction) Id() uint8 {
|
|
||||||
return uint8(sk)
|
|
||||||
}
|
|
||||||
|
|
||||||
var keySizeByID = map[uint8]int{
|
|
||||||
TripleDES.Id(): 24,
|
|
||||||
CAST5.Id(): cast5.KeySize,
|
|
||||||
AES128.Id(): 16,
|
|
||||||
AES192.Id(): 24,
|
|
||||||
AES256.Id(): 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeySize returns the key size, in bytes, of cipher.
|
|
||||||
func (cipher CipherFunction) KeySize() int {
|
|
||||||
switch cipher {
|
|
||||||
case TripleDES:
|
|
||||||
return 24
|
|
||||||
case CAST5:
|
|
||||||
return cast5.KeySize
|
|
||||||
case AES128:
|
|
||||||
return 16
|
|
||||||
case AES192:
|
|
||||||
return 24
|
|
||||||
case AES256:
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockSize returns the block size, in bytes, of cipher.
|
|
||||||
func (cipher CipherFunction) BlockSize() int {
|
|
||||||
switch cipher {
|
|
||||||
case TripleDES:
|
|
||||||
return des.BlockSize
|
|
||||||
case CAST5:
|
|
||||||
return 8
|
|
||||||
case AES128, AES192, AES256:
|
|
||||||
return 16
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a fresh instance of the given cipher.
|
|
||||||
func (cipher CipherFunction) New(key []byte) (block cipher.Block) {
|
|
||||||
var err error
|
|
||||||
switch cipher {
|
|
||||||
case TripleDES:
|
|
||||||
block, err = des.NewTripleDESCipher(key)
|
|
||||||
case CAST5:
|
|
||||||
block, err = cast5.NewCipher(key)
|
|
||||||
case AES128, AES192, AES256:
|
|
||||||
block, err = aes.NewCipher(key)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
// Copyright 2017 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 algorithm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hash is an official hash function algorithm. See RFC 4880, section 9.4.
|
|
||||||
type Hash interface {
|
|
||||||
// Id returns the algorithm ID, as a byte, of Hash.
|
|
||||||
Id() uint8
|
|
||||||
// Available reports whether the given hash function is linked into the binary.
|
|
||||||
Available() bool
|
|
||||||
// HashFunc simply returns the value of h so that Hash implements SignerOpts.
|
|
||||||
HashFunc() crypto.Hash
|
|
||||||
// New returns a new hash.Hash calculating the given hash function. New
|
|
||||||
// panics if the hash function is not linked into the binary.
|
|
||||||
New() hash.Hash
|
|
||||||
// Size returns the length, in bytes, of a digest resulting from the given
|
|
||||||
// hash function. It doesn't require that the hash function in question be
|
|
||||||
// linked into the program.
|
|
||||||
Size() int
|
|
||||||
// String is the name of the hash function corresponding to the given
|
|
||||||
// OpenPGP hash id.
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following vars mirror the crypto/Hash supported hash functions.
|
|
||||||
var (
|
|
||||||
SHA1 Hash = cryptoHash{2, crypto.SHA1}
|
|
||||||
SHA256 Hash = cryptoHash{8, crypto.SHA256}
|
|
||||||
SHA384 Hash = cryptoHash{9, crypto.SHA384}
|
|
||||||
SHA512 Hash = cryptoHash{10, crypto.SHA512}
|
|
||||||
SHA224 Hash = cryptoHash{11, crypto.SHA224}
|
|
||||||
SHA3_256 Hash = cryptoHash{12, crypto.SHA3_256}
|
|
||||||
SHA3_512 Hash = cryptoHash{14, crypto.SHA3_512}
|
|
||||||
)
|
|
||||||
|
|
||||||
// HashById represents the different hash functions specified for OpenPGP. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-14
|
|
||||||
var (
|
|
||||||
HashById = map[uint8]Hash{
|
|
||||||
SHA256.Id(): SHA256,
|
|
||||||
SHA384.Id(): SHA384,
|
|
||||||
SHA512.Id(): SHA512,
|
|
||||||
SHA224.Id(): SHA224,
|
|
||||||
SHA3_256.Id(): SHA3_256,
|
|
||||||
SHA3_512.Id(): SHA3_512,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// cryptoHash contains pairs relating OpenPGP's hash identifier with
|
|
||||||
// Go's crypto.Hash type. See RFC 4880, section 9.4.
|
|
||||||
type cryptoHash struct {
|
|
||||||
id uint8
|
|
||||||
crypto.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id returns the algorithm ID, as a byte, of cryptoHash.
|
|
||||||
func (h cryptoHash) Id() uint8 {
|
|
||||||
return h.id
|
|
||||||
}
|
|
||||||
|
|
||||||
var hashNames = map[uint8]string{
|
|
||||||
SHA256.Id(): "SHA256",
|
|
||||||
SHA384.Id(): "SHA384",
|
|
||||||
SHA512.Id(): "SHA512",
|
|
||||||
SHA224.Id(): "SHA224",
|
|
||||||
SHA3_256.Id(): "SHA3-256",
|
|
||||||
SHA3_512.Id(): "SHA3-512",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h cryptoHash) String() string {
|
|
||||||
s, ok := hashNames[h.id]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("Unsupported hash function %d", h.id))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
|
|
||||||
// hash id.
|
|
||||||
func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
|
|
||||||
if hash, ok := HashById[id]; ok {
|
|
||||||
return hash.HashFunc(), true
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashIdToHashWithSha1 returns a crypto.Hash which corresponds to the given OpenPGP
|
|
||||||
// hash id, allowing sha1.
|
|
||||||
func HashIdToHashWithSha1(id byte) (h crypto.Hash, ok bool) {
|
|
||||||
if hash, ok := HashById[id]; ok {
|
|
||||||
return hash.HashFunc(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
if id == SHA1.Id() {
|
|
||||||
return SHA1.HashFunc(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashIdToString returns the name of the hash function corresponding to the
|
|
||||||
// given OpenPGP hash id.
|
|
||||||
func HashIdToString(id byte) (name string, ok bool) {
|
|
||||||
if hash, ok := HashById[id]; ok {
|
|
||||||
return hash.String(), true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashToHashId returns an OpenPGP hash id which corresponds the given Hash.
|
|
||||||
func HashToHashId(h crypto.Hash) (id byte, ok bool) {
|
|
||||||
for id, hash := range HashById {
|
|
||||||
if hash.HashFunc() == h {
|
|
||||||
return id, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashToHashIdWithSha1 returns an OpenPGP hash id which corresponds the given Hash,
|
|
||||||
// allowing instances of SHA1
|
|
||||||
func HashToHashIdWithSha1(h crypto.Hash) (id byte, ok bool) {
|
|
||||||
for id, hash := range HashById {
|
|
||||||
if hash.HashFunc() == h {
|
|
||||||
return id, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if h == SHA1.HashFunc() {
|
|
||||||
return SHA1.Id(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, false
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
|
||||||
package ecc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
x25519lib "github.com/cloudflare/circl/dh/x25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
type curve25519 struct{}
|
|
||||||
|
|
||||||
func NewCurve25519() *curve25519 {
|
|
||||||
return &curve25519{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *curve25519) GetCurveName() string {
|
|
||||||
return "curve25519"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBytePoint encodes the public point from native format, adding the prefix.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
|
||||||
func (c *curve25519) MarshalBytePoint(point []byte) []byte {
|
|
||||||
return append([]byte{0x40}, point...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBytePoint decodes the public point to native format, removing the prefix.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
|
||||||
func (c *curve25519) UnmarshalBytePoint(point []byte) []byte {
|
|
||||||
if len(point) != x25519lib.Size+1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove prefix
|
|
||||||
return point[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalByteSecret encodes the secret scalar from native format.
|
|
||||||
// Note that the EC secret scalar differs from the definition of public keys in
|
|
||||||
// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
|
|
||||||
// more uniform with how big integers are represented in OpenPGP, and (2) the
|
|
||||||
// leading zeros are truncated.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
|
|
||||||
// Note that leading zero bytes are stripped later when encoding as an MPI.
|
|
||||||
func (c *curve25519) MarshalByteSecret(secret []byte) []byte {
|
|
||||||
d := make([]byte, x25519lib.Size)
|
|
||||||
copyReversed(d, secret)
|
|
||||||
|
|
||||||
// The following ensures that the private key is a number of the form
|
|
||||||
// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of
|
|
||||||
// the curve.
|
|
||||||
//
|
|
||||||
// This masking is done internally in the underlying lib and so is unnecessary
|
|
||||||
// for security, but OpenPGP implementations require that private keys be
|
|
||||||
// pre-masked.
|
|
||||||
d[0] &= 127
|
|
||||||
d[0] |= 64
|
|
||||||
d[31] &= 248
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalByteSecret decodes the secret scalar from native format.
|
|
||||||
// Note that the EC secret scalar differs from the definition of public keys in
|
|
||||||
// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
|
|
||||||
// more uniform with how big integers are represented in OpenPGP, and (2) the
|
|
||||||
// leading zeros are truncated.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
|
|
||||||
func (c *curve25519) UnmarshalByteSecret(d []byte) []byte {
|
|
||||||
if len(d) > x25519lib.Size {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure truncated leading bytes are re-added
|
|
||||||
secret := make([]byte, x25519lib.Size)
|
|
||||||
copyReversed(secret, d)
|
|
||||||
|
|
||||||
return secret
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateKeyPairBytes Generates a private-public key-pair.
|
|
||||||
// 'priv' is a private key; a little-endian scalar belonging to the set
|
|
||||||
// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of the
|
|
||||||
// curve. 'pub' is simply 'priv' * G where G is the base point.
|
|
||||||
// See https://cr.yp.to/ecdh.html and RFC7748, sec 5.
|
|
||||||
func (c *curve25519) generateKeyPairBytes(rand io.Reader) (priv, pub x25519lib.Key, err error) {
|
|
||||||
_, err = io.ReadFull(rand, priv[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
x25519lib.KeyGen(&pub, &priv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *curve25519) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) {
|
|
||||||
priv, pub, err := c.generateKeyPairBytes(rand)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return pub[:], priv[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) MaskSecret(secret []byte) []byte {
|
|
||||||
return secret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *curve25519) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
|
|
||||||
// RFC6637 §8: "Generate an ephemeral key pair {v, V=vG}"
|
|
||||||
// ephemeralPrivate corresponds to `v`.
|
|
||||||
// ephemeralPublic corresponds to `V`.
|
|
||||||
ephemeralPrivate, ephemeralPublic, err := c.generateKeyPairBytes(rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC6637 §8: "Obtain the authenticated recipient public key R"
|
|
||||||
// pubKey corresponds to `R`.
|
|
||||||
var pubKey x25519lib.Key
|
|
||||||
copy(pubKey[:], point)
|
|
||||||
|
|
||||||
// RFC6637 §8: "Compute the shared point S = vR"
|
|
||||||
// "VB = convert point V to the octet string"
|
|
||||||
// sharedPoint corresponds to `VB`.
|
|
||||||
var sharedPoint x25519lib.Key
|
|
||||||
x25519lib.Shared(&sharedPoint, &ephemeralPrivate, &pubKey)
|
|
||||||
|
|
||||||
return ephemeralPublic[:], sharedPoint[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *curve25519) Decaps(vsG, secret []byte) (sharedSecret []byte, err error) {
|
|
||||||
var ephemeralPublic, decodedPrivate, sharedPoint x25519lib.Key
|
|
||||||
// RFC6637 §8: "The decryption is the inverse of the method given."
|
|
||||||
// All quoted descriptions in comments below describe encryption, and
|
|
||||||
// the reverse is performed.
|
|
||||||
// vsG corresponds to `VB` in RFC6637 §8 .
|
|
||||||
|
|
||||||
// RFC6637 §8: "VB = convert point V to the octet string"
|
|
||||||
copy(ephemeralPublic[:], vsG)
|
|
||||||
|
|
||||||
// decodedPrivate corresponds to `r` in RFC6637 §8 .
|
|
||||||
copy(decodedPrivate[:], secret)
|
|
||||||
|
|
||||||
// RFC6637 §8: "Note that the recipient obtains the shared secret by calculating
|
|
||||||
// S = rV = rvG, where (r,R) is the recipient's key pair."
|
|
||||||
// sharedPoint corresponds to `S`.
|
|
||||||
x25519lib.Shared(&sharedPoint, &decodedPrivate, &ephemeralPublic)
|
|
||||||
|
|
||||||
return sharedPoint[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *curve25519) ValidateECDH(point []byte, secret []byte) (err error) {
|
|
||||||
var pk, sk x25519lib.Key
|
|
||||||
copy(sk[:], secret)
|
|
||||||
x25519lib.KeyGen(&pk, &sk)
|
|
||||||
|
|
||||||
if subtle.ConstantTimeCompare(point, pk[:]) == 0 {
|
|
||||||
return errors.KeyInvalidError("ecc: invalid curve25519 public point")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyReversed(out []byte, in []byte) {
|
|
||||||
l := len(in)
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
out[i] = in[l-i-1]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
|
||||||
package ecc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"github.com/ProtonMail/go-crypto/bitcurves"
|
|
||||||
"github.com/ProtonMail/go-crypto/brainpool"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CurveInfo struct {
|
|
||||||
GenName string
|
|
||||||
Oid *encoding.OID
|
|
||||||
Curve Curve
|
|
||||||
}
|
|
||||||
|
|
||||||
var Curves = []CurveInfo{
|
|
||||||
{
|
|
||||||
// NIST P-256
|
|
||||||
GenName: "P256",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}),
|
|
||||||
Curve: NewGenericCurve(elliptic.P256()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// NIST P-384
|
|
||||||
GenName: "P384",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x22}),
|
|
||||||
Curve: NewGenericCurve(elliptic.P384()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// NIST P-521
|
|
||||||
GenName: "P521",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x23}),
|
|
||||||
Curve: NewGenericCurve(elliptic.P521()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// SecP256k1
|
|
||||||
GenName: "SecP256k1",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x0A}),
|
|
||||||
Curve: NewGenericCurve(bitcurves.S256()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Curve25519
|
|
||||||
GenName: "Curve25519",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}),
|
|
||||||
Curve: NewCurve25519(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// X448
|
|
||||||
GenName: "Curve448",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x6F}),
|
|
||||||
Curve: NewX448(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Ed25519
|
|
||||||
GenName: "Curve25519",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01}),
|
|
||||||
Curve: NewEd25519(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Ed448
|
|
||||||
GenName: "Curve448",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x71}),
|
|
||||||
Curve: NewEd448(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// BrainpoolP256r1
|
|
||||||
GenName: "BrainpoolP256",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}),
|
|
||||||
Curve: NewGenericCurve(brainpool.P256r1()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// BrainpoolP384r1
|
|
||||||
GenName: "BrainpoolP384",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}),
|
|
||||||
Curve: NewGenericCurve(brainpool.P384r1()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// BrainpoolP512r1
|
|
||||||
GenName: "BrainpoolP512",
|
|
||||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}),
|
|
||||||
Curve: NewGenericCurve(brainpool.P512r1()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindByCurve(curve Curve) *CurveInfo {
|
|
||||||
for _, curveInfo := range Curves {
|
|
||||||
if curveInfo.Curve.GetCurveName() == curve.GetCurveName() {
|
|
||||||
return &curveInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindByOid(oid encoding.Field) *CurveInfo {
|
|
||||||
var rawBytes = oid.Bytes()
|
|
||||||
for _, curveInfo := range Curves {
|
|
||||||
if bytes.Equal(curveInfo.Oid.Bytes(), rawBytes) {
|
|
||||||
return &curveInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindEdDSAByGenName(curveGenName string) EdDSACurve {
|
|
||||||
for _, curveInfo := range Curves {
|
|
||||||
if curveInfo.GenName == curveGenName {
|
|
||||||
curve, ok := curveInfo.Curve.(EdDSACurve)
|
|
||||||
if ok {
|
|
||||||
return curve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindECDSAByGenName(curveGenName string) ECDSACurve {
|
|
||||||
for _, curveInfo := range Curves {
|
|
||||||
if curveInfo.GenName == curveGenName {
|
|
||||||
curve, ok := curveInfo.Curve.(ECDSACurve)
|
|
||||||
if ok {
|
|
||||||
return curve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindECDHByGenName(curveGenName string) ECDHCurve {
|
|
||||||
for _, curveInfo := range Curves {
|
|
||||||
if curveInfo.GenName == curveGenName {
|
|
||||||
curve, ok := curveInfo.Curve.(ECDHCurve)
|
|
||||||
if ok {
|
|
||||||
return curve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
|
||||||
package ecc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Curve interface {
|
|
||||||
GetCurveName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ECDSACurve interface {
|
|
||||||
Curve
|
|
||||||
MarshalIntegerPoint(x, y *big.Int) []byte
|
|
||||||
UnmarshalIntegerPoint([]byte) (x, y *big.Int)
|
|
||||||
MarshalIntegerSecret(d *big.Int) []byte
|
|
||||||
UnmarshalIntegerSecret(d []byte) *big.Int
|
|
||||||
GenerateECDSA(rand io.Reader) (x, y, secret *big.Int, err error)
|
|
||||||
Sign(rand io.Reader, x, y, d *big.Int, hash []byte) (r, s *big.Int, err error)
|
|
||||||
Verify(x, y *big.Int, hash []byte, r, s *big.Int) bool
|
|
||||||
ValidateECDSA(x, y *big.Int, secret []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type EdDSACurve interface {
|
|
||||||
Curve
|
|
||||||
MarshalBytePoint(x []byte) []byte
|
|
||||||
UnmarshalBytePoint([]byte) (x []byte)
|
|
||||||
MarshalByteSecret(d []byte) []byte
|
|
||||||
UnmarshalByteSecret(d []byte) []byte
|
|
||||||
MarshalSignature(sig []byte) (r, s []byte)
|
|
||||||
UnmarshalSignature(r, s []byte) (sig []byte)
|
|
||||||
GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error)
|
|
||||||
Sign(publicKey, privateKey, message []byte) (sig []byte, err error)
|
|
||||||
Verify(publicKey, message, sig []byte) bool
|
|
||||||
ValidateEdDSA(publicKey, privateKey []byte) (err error)
|
|
||||||
}
|
|
||||||
type ECDHCurve interface {
|
|
||||||
Curve
|
|
||||||
MarshalBytePoint([]byte) (encoded []byte)
|
|
||||||
UnmarshalBytePoint(encoded []byte) []byte
|
|
||||||
MarshalByteSecret(d []byte) []byte
|
|
||||||
UnmarshalByteSecret(d []byte) []byte
|
|
||||||
GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error)
|
|
||||||
Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error)
|
|
||||||
Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error)
|
|
||||||
ValidateECDH(public []byte, secret []byte) error
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
|
||||||
package ecc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
ed25519lib "github.com/cloudflare/circl/sign/ed25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ed25519Size = 32
|
|
||||||
|
|
||||||
type ed25519 struct{}
|
|
||||||
|
|
||||||
func NewEd25519() *ed25519 {
|
|
||||||
return &ed25519{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed25519) GetCurveName() string {
|
|
||||||
return "ed25519"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBytePoint encodes the public point from native format, adding the prefix.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed25519) MarshalBytePoint(x []byte) []byte {
|
|
||||||
return append([]byte{0x40}, x...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBytePoint decodes a point from prefixed format to native.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed25519) UnmarshalBytePoint(point []byte) (x []byte) {
|
|
||||||
if len(point) != ed25519lib.PublicKeySize+1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return unprefixed
|
|
||||||
return point[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalByteSecret encodes a scalar in native format.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed25519) MarshalByteSecret(d []byte) []byte {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalByteSecret decodes a scalar in native format and re-adds the stripped leading zeroes
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed25519) UnmarshalByteSecret(s []byte) (d []byte) {
|
|
||||||
if len(s) > ed25519lib.SeedSize {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle stripped leading zeroes
|
|
||||||
d = make([]byte, ed25519lib.SeedSize)
|
|
||||||
copy(d[ed25519lib.SeedSize-len(s):], s)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalSignature splits a signature in R and S.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.1
|
|
||||||
func (c *ed25519) MarshalSignature(sig []byte) (r, s []byte) {
|
|
||||||
return sig[:ed25519Size], sig[ed25519Size:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalSignature decodes R and S in the native format, re-adding the stripped leading zeroes
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.1
|
|
||||||
func (c *ed25519) UnmarshalSignature(r, s []byte) (sig []byte) {
|
|
||||||
// Check size
|
|
||||||
if len(r) > 32 || len(s) > 32 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sig = make([]byte, ed25519lib.SignatureSize)
|
|
||||||
|
|
||||||
// Handle stripped leading zeroes
|
|
||||||
copy(sig[ed25519Size-len(r):ed25519Size], r)
|
|
||||||
copy(sig[ed25519lib.SignatureSize-len(s):], s)
|
|
||||||
return sig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed25519) GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error) {
|
|
||||||
pk, sk, err := ed25519lib.GenerateKey(rand)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pk, sk[:ed25519lib.SeedSize], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEd25519Sk(publicKey, privateKey []byte) ed25519lib.PrivateKey {
|
|
||||||
return append(privateKey, publicKey...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed25519) Sign(publicKey, privateKey, message []byte) (sig []byte, err error) {
|
|
||||||
sig = ed25519lib.Sign(getEd25519Sk(publicKey, privateKey), message)
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed25519) Verify(publicKey, message, sig []byte) bool {
|
|
||||||
return ed25519lib.Verify(publicKey, message, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed25519) ValidateEdDSA(publicKey, privateKey []byte) (err error) {
|
|
||||||
priv := getEd25519Sk(publicKey, privateKey)
|
|
||||||
expectedPriv := ed25519lib.NewKeyFromSeed(priv.Seed())
|
|
||||||
if subtle.ConstantTimeCompare(priv, expectedPriv) == 0 {
|
|
||||||
return errors.KeyInvalidError("ecc: invalid ed25519 secret")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
|
||||||
package ecc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
ed448lib "github.com/cloudflare/circl/sign/ed448"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ed448 struct{}
|
|
||||||
|
|
||||||
func NewEd448() *ed448 {
|
|
||||||
return &ed448{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed448) GetCurveName() string {
|
|
||||||
return "ed448"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBytePoint encodes the public point from native format, adding the prefix.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed448) MarshalBytePoint(x []byte) []byte {
|
|
||||||
// Return prefixed
|
|
||||||
return append([]byte{0x40}, x...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBytePoint decodes a point from prefixed format to native.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed448) UnmarshalBytePoint(point []byte) (x []byte) {
|
|
||||||
if len(point) != ed448lib.PublicKeySize+1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip prefix
|
|
||||||
return point[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalByteSecret encoded a scalar from native format to prefixed.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed448) MarshalByteSecret(d []byte) []byte {
|
|
||||||
// Return prefixed
|
|
||||||
return append([]byte{0x40}, d...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalByteSecret decodes a scalar from prefixed format to native.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
|
||||||
func (c *ed448) UnmarshalByteSecret(s []byte) (d []byte) {
|
|
||||||
// Check prefixed size
|
|
||||||
if len(s) != ed448lib.SeedSize+1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip prefix
|
|
||||||
return s[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalSignature splits a signature in R and S, where R is in prefixed native format and
|
|
||||||
// S is an MPI with value zero.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2
|
|
||||||
func (c *ed448) MarshalSignature(sig []byte) (r, s []byte) {
|
|
||||||
return append([]byte{0x40}, sig...), []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalSignature decodes R and S in the native format. Only R is used, in prefixed native format.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2
|
|
||||||
func (c *ed448) UnmarshalSignature(r, s []byte) (sig []byte) {
|
|
||||||
if len(r) != ed448lib.SignatureSize+1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return r[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed448) GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error) {
|
|
||||||
pk, sk, err := ed448lib.GenerateKey(rand)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pk, sk[:ed448lib.SeedSize], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEd448Sk(publicKey, privateKey []byte) ed448lib.PrivateKey {
|
|
||||||
return append(privateKey, publicKey...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed448) Sign(publicKey, privateKey, message []byte) (sig []byte, err error) {
|
|
||||||
// Ed448 is used with the empty string as a context string.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7
|
|
||||||
sig = ed448lib.Sign(getEd448Sk(publicKey, privateKey), message, "")
|
|
||||||
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed448) Verify(publicKey, message, sig []byte) bool {
|
|
||||||
// Ed448 is used with the empty string as a context string.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7
|
|
||||||
return ed448lib.Verify(publicKey, message, sig, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ed448) ValidateEdDSA(publicKey, privateKey []byte) (err error) {
|
|
||||||
priv := getEd448Sk(publicKey, privateKey)
|
|
||||||
expectedPriv := ed448lib.NewKeyFromSeed(priv.Seed())
|
|
||||||
if subtle.ConstantTimeCompare(priv, expectedPriv) == 0 {
|
|
||||||
return errors.KeyInvalidError("ecc: invalid ed448 secret")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
|
||||||
package ecc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"fmt"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
type genericCurve struct {
|
|
||||||
Curve elliptic.Curve
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGenericCurve(c elliptic.Curve) *genericCurve {
|
|
||||||
return &genericCurve{
|
|
||||||
Curve: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) GetCurveName() string {
|
|
||||||
return c.Curve.Params().Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) MarshalBytePoint(point []byte) []byte {
|
|
||||||
return point
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) UnmarshalBytePoint(point []byte) []byte {
|
|
||||||
return point
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) MarshalIntegerPoint(x, y *big.Int) []byte {
|
|
||||||
return elliptic.Marshal(c.Curve, x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) UnmarshalIntegerPoint(point []byte) (x, y *big.Int) {
|
|
||||||
return elliptic.Unmarshal(c.Curve, point)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) MarshalByteSecret(d []byte) []byte {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) UnmarshalByteSecret(d []byte) []byte {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) MarshalIntegerSecret(d *big.Int) []byte {
|
|
||||||
return d.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) UnmarshalIntegerSecret(d []byte) *big.Int {
|
|
||||||
return new(big.Int).SetBytes(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) GenerateECDH(rand io.Reader) (point, secret []byte, err error) {
|
|
||||||
secret, x, y, err := elliptic.GenerateKey(c.Curve, rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
point = elliptic.Marshal(c.Curve, x, y)
|
|
||||||
return point, secret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) GenerateECDSA(rand io.Reader) (x, y, secret *big.Int, err error) {
|
|
||||||
priv, err := ecdsa.GenerateKey(c.Curve, rand)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return priv.X, priv.Y, priv.D, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
|
|
||||||
xP, yP := elliptic.Unmarshal(c.Curve, point)
|
|
||||||
if xP == nil {
|
|
||||||
panic("invalid point")
|
|
||||||
}
|
|
||||||
|
|
||||||
d, x, y, err := elliptic.GenerateKey(c.Curve, rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vsG := elliptic.Marshal(c.Curve, x, y)
|
|
||||||
zbBig, _ := c.Curve.ScalarMult(xP, yP, d)
|
|
||||||
|
|
||||||
byteLen := (c.Curve.Params().BitSize + 7) >> 3
|
|
||||||
zb := make([]byte, byteLen)
|
|
||||||
zbBytes := zbBig.Bytes()
|
|
||||||
copy(zb[byteLen-len(zbBytes):], zbBytes)
|
|
||||||
|
|
||||||
return vsG, zb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error) {
|
|
||||||
x, y := elliptic.Unmarshal(c.Curve, ephemeral)
|
|
||||||
zbBig, _ := c.Curve.ScalarMult(x, y, secret)
|
|
||||||
byteLen := (c.Curve.Params().BitSize + 7) >> 3
|
|
||||||
zb := make([]byte, byteLen)
|
|
||||||
zbBytes := zbBig.Bytes()
|
|
||||||
copy(zb[byteLen-len(zbBytes):], zbBytes)
|
|
||||||
|
|
||||||
return zb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) Sign(rand io.Reader, x, y, d *big.Int, hash []byte) (r, s *big.Int, err error) {
|
|
||||||
priv := &ecdsa.PrivateKey{D: d, PublicKey: ecdsa.PublicKey{X: x, Y: y, Curve: c.Curve}}
|
|
||||||
return ecdsa.Sign(rand, priv, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) Verify(x, y *big.Int, hash []byte, r, s *big.Int) bool {
|
|
||||||
pub := &ecdsa.PublicKey{X: x, Y: y, Curve: c.Curve}
|
|
||||||
return ecdsa.Verify(pub, hash, r, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) validate(xP, yP *big.Int, secret []byte) error {
|
|
||||||
// the public point should not be at infinity (0,0)
|
|
||||||
zero := new(big.Int)
|
|
||||||
if xP.Cmp(zero) == 0 && yP.Cmp(zero) == 0 {
|
|
||||||
return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): infinity point", c.Curve.Params().Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-derive the public point Q' = (X,Y) = dG
|
|
||||||
// to compare to declared Q in public key
|
|
||||||
expectedX, expectedY := c.Curve.ScalarBaseMult(secret)
|
|
||||||
if xP.Cmp(expectedX) != 0 || yP.Cmp(expectedY) != 0 {
|
|
||||||
return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): invalid point", c.Curve.Params().Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) ValidateECDSA(xP, yP *big.Int, secret []byte) error {
|
|
||||||
return c.validate(xP, yP, secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *genericCurve) ValidateECDH(point []byte, secret []byte) error {
|
|
||||||
xP, yP := elliptic.Unmarshal(c.Curve, point)
|
|
||||||
if xP == nil {
|
|
||||||
return errors.KeyInvalidError(fmt.Sprintf("ecc (%s): invalid point", c.Curve.Params().Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.validate(xP, yP, secret)
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
|
||||||
package ecc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
x448lib "github.com/cloudflare/circl/dh/x448"
|
|
||||||
)
|
|
||||||
|
|
||||||
type x448 struct{}
|
|
||||||
|
|
||||||
func NewX448() *x448 {
|
|
||||||
return &x448{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *x448) GetCurveName() string {
|
|
||||||
return "x448"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBytePoint encodes the public point from native format, adding the prefix.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
|
||||||
func (c *x448) MarshalBytePoint(point []byte) []byte {
|
|
||||||
return append([]byte{0x40}, point...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBytePoint decodes a point from prefixed format to native.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
|
||||||
func (c *x448) UnmarshalBytePoint(point []byte) []byte {
|
|
||||||
if len(point) != x448lib.Size+1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return point[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalByteSecret encoded a scalar from native format to prefixed.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.2
|
|
||||||
func (c *x448) MarshalByteSecret(d []byte) []byte {
|
|
||||||
return append([]byte{0x40}, d...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalByteSecret decodes a scalar from prefixed format to native.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.2
|
|
||||||
func (c *x448) UnmarshalByteSecret(d []byte) []byte {
|
|
||||||
if len(d) != x448lib.Size+1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store without prefix
|
|
||||||
return d[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *x448) generateKeyPairBytes(rand io.Reader) (sk, pk x448lib.Key, err error) {
|
|
||||||
if _, err = rand.Read(sk[:]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
x448lib.KeyGen(&pk, &sk)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *x448) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) {
|
|
||||||
priv, pub, err := c.generateKeyPairBytes(rand)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return pub[:], priv[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *x448) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
|
|
||||||
var pk, ss x448lib.Key
|
|
||||||
seed, e, err := c.generateKeyPairBytes(rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
copy(pk[:], point)
|
|
||||||
x448lib.Shared(&ss, &seed, &pk)
|
|
||||||
|
|
||||||
return e[:], ss[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *x448) Decaps(ephemeral, secret []byte) (sharedSecret []byte, err error) {
|
|
||||||
var ss, sk, e x448lib.Key
|
|
||||||
|
|
||||||
copy(sk[:], secret)
|
|
||||||
copy(e[:], ephemeral)
|
|
||||||
x448lib.Shared(&ss, &sk, &e)
|
|
||||||
|
|
||||||
return ss[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *x448) ValidateECDH(point []byte, secret []byte) error {
|
|
||||||
var sk, pk, expectedPk x448lib.Key
|
|
||||||
|
|
||||||
copy(pk[:], point)
|
|
||||||
copy(sk[:], secret)
|
|
||||||
x448lib.KeyGen(&expectedPk, &sk)
|
|
||||||
|
|
||||||
if subtle.ConstantTimeCompare(expectedPk[:], pk[:]) == 0 {
|
|
||||||
return errors.KeyInvalidError("ecc: invalid curve25519 public point")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
// Copyright 2017 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 encoding implements openpgp packet field encodings as specified in
|
|
||||||
// RFC 4880 and 6637.
|
|
||||||
package encoding
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// Field is an encoded field of an openpgp packet.
|
|
||||||
type Field interface {
|
|
||||||
// Bytes returns the decoded data.
|
|
||||||
Bytes() []byte
|
|
||||||
|
|
||||||
// BitLength is the size in bits of the decoded data.
|
|
||||||
BitLength() uint16
|
|
||||||
|
|
||||||
// EncodedBytes returns the encoded data.
|
|
||||||
EncodedBytes() []byte
|
|
||||||
|
|
||||||
// EncodedLength is the size in bytes of the encoded data.
|
|
||||||
EncodedLength() uint16
|
|
||||||
|
|
||||||
// ReadFrom reads the next Field from r.
|
|
||||||
ReadFrom(r io.Reader) (int64, error)
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
// Copyright 2017 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 encoding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"math/bits"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An MPI is used to store the contents of a big integer, along with the bit
|
|
||||||
// length that was specified in the original input. This allows the MPI to be
|
|
||||||
// reserialized exactly.
|
|
||||||
type MPI struct {
|
|
||||||
bytes []byte
|
|
||||||
bitLength uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMPI returns a MPI initialized with bytes.
|
|
||||||
func NewMPI(bytes []byte) *MPI {
|
|
||||||
for len(bytes) != 0 && bytes[0] == 0 {
|
|
||||||
bytes = bytes[1:]
|
|
||||||
}
|
|
||||||
if len(bytes) == 0 {
|
|
||||||
bitLength := uint16(0)
|
|
||||||
return &MPI{bytes, bitLength}
|
|
||||||
}
|
|
||||||
bitLength := 8*uint16(len(bytes)-1) + uint16(bits.Len8(bytes[0]))
|
|
||||||
return &MPI{bytes, bitLength}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the decoded data.
|
|
||||||
func (m *MPI) Bytes() []byte {
|
|
||||||
return m.bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// BitLength is the size in bits of the decoded data.
|
|
||||||
func (m *MPI) BitLength() uint16 {
|
|
||||||
return m.bitLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodedBytes returns the encoded data.
|
|
||||||
func (m *MPI) EncodedBytes() []byte {
|
|
||||||
return append([]byte{byte(m.bitLength >> 8), byte(m.bitLength)}, m.bytes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodedLength is the size in bytes of the encoded data.
|
|
||||||
func (m *MPI) EncodedLength() uint16 {
|
|
||||||
return uint16(2 + len(m.bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom reads into m the next MPI from r.
|
|
||||||
func (m *MPI) ReadFrom(r io.Reader) (int64, error) {
|
|
||||||
var buf [2]byte
|
|
||||||
n, err := io.ReadFull(r, buf[0:])
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return int64(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.bitLength = uint16(buf[0])<<8 | uint16(buf[1])
|
|
||||||
m.bytes = make([]byte, (int(m.bitLength)+7)/8)
|
|
||||||
|
|
||||||
nn, err := io.ReadFull(r, m.bytes)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove leading zero bytes from malformed GnuPG encoded MPIs:
|
|
||||||
// https://bugs.gnupg.org/gnupg/issue1853
|
|
||||||
// for _, b := range m.bytes {
|
|
||||||
// if b != 0 {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// m.bytes = m.bytes[1:]
|
|
||||||
// m.bitLength -= 8
|
|
||||||
// }
|
|
||||||
|
|
||||||
return int64(n) + int64(nn), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBig initializes m with the bits from n.
|
|
||||||
func (m *MPI) SetBig(n *big.Int) *MPI {
|
|
||||||
m.bytes = n.Bytes()
|
|
||||||
m.bitLength = uint16(n.BitLen())
|
|
||||||
return m
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
// Copyright 2017 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 encoding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OID is used to store a variable-length field with a one-octet size
|
|
||||||
// prefix. See https://tools.ietf.org/html/rfc6637#section-9.
|
|
||||||
type OID struct {
|
|
||||||
bytes []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// maxOID is the maximum number of bytes in a OID.
|
|
||||||
maxOID = 254
|
|
||||||
// reservedOIDLength1 and reservedOIDLength2 are OID lengths that the RFC
|
|
||||||
// specifies are reserved.
|
|
||||||
reservedOIDLength1 = 0
|
|
||||||
reservedOIDLength2 = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewOID returns a OID initialized with bytes.
|
|
||||||
func NewOID(bytes []byte) *OID {
|
|
||||||
switch len(bytes) {
|
|
||||||
case reservedOIDLength1, reservedOIDLength2:
|
|
||||||
panic("encoding: NewOID argument length is reserved")
|
|
||||||
default:
|
|
||||||
if len(bytes) > maxOID {
|
|
||||||
panic("encoding: NewOID argument too large")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &OID{
|
|
||||||
bytes: bytes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the decoded data.
|
|
||||||
func (o *OID) Bytes() []byte {
|
|
||||||
return o.bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// BitLength is the size in bits of the decoded data.
|
|
||||||
func (o *OID) BitLength() uint16 {
|
|
||||||
return uint16(len(o.bytes) * 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodedBytes returns the encoded data.
|
|
||||||
func (o *OID) EncodedBytes() []byte {
|
|
||||||
return append([]byte{byte(len(o.bytes))}, o.bytes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodedLength is the size in bytes of the encoded data.
|
|
||||||
func (o *OID) EncodedLength() uint16 {
|
|
||||||
return uint16(1 + len(o.bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom reads into b the next OID from r.
|
|
||||||
func (o *OID) ReadFrom(r io.Reader) (int64, error) {
|
|
||||||
var buf [1]byte
|
|
||||||
n, err := io.ReadFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return int64(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch buf[0] {
|
|
||||||
case reservedOIDLength1, reservedOIDLength2:
|
|
||||||
return int64(n), errors.UnsupportedError("reserved for future extensions")
|
|
||||||
}
|
|
||||||
|
|
||||||
o.bytes = make([]byte, buf[0])
|
|
||||||
|
|
||||||
nn, err := io.ReadFull(r, o.bytes)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
return int64(n) + int64(nn), err
|
|
||||||
}
|
|
@ -1,842 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package openpgp
|
|
||||||
|
|
||||||
import (
|
|
||||||
goerrors "errors"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKeyType is the armor type for a PGP public key.
|
|
||||||
var PublicKeyType = "PGP PUBLIC KEY BLOCK"
|
|
||||||
|
|
||||||
// PrivateKeyType is the armor type for a PGP private key.
|
|
||||||
var PrivateKeyType = "PGP PRIVATE KEY BLOCK"
|
|
||||||
|
|
||||||
// An Entity represents the components of an OpenPGP key: a primary public key
|
|
||||||
// (which must be a signing key), one or more identities claimed by that key,
|
|
||||||
// and zero or more subkeys, which may be encryption keys.
|
|
||||||
type Entity struct {
|
|
||||||
PrimaryKey *packet.PublicKey
|
|
||||||
PrivateKey *packet.PrivateKey
|
|
||||||
Identities map[string]*Identity // indexed by Identity.Name
|
|
||||||
Revocations []*packet.Signature
|
|
||||||
Subkeys []Subkey
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Identity represents an identity claimed by an Entity and zero or more
|
|
||||||
// assertions by other entities about that claim.
|
|
||||||
type Identity struct {
|
|
||||||
Name string // by convention, has the form "Full Name (comment) <email@example.com>"
|
|
||||||
UserId *packet.UserId
|
|
||||||
SelfSignature *packet.Signature
|
|
||||||
Revocations []*packet.Signature
|
|
||||||
Signatures []*packet.Signature // all (potentially unverified) self-signatures, revocations, and third-party signatures
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Subkey is an additional public key in an Entity. Subkeys can be used for
|
|
||||||
// encryption.
|
|
||||||
type Subkey struct {
|
|
||||||
PublicKey *packet.PublicKey
|
|
||||||
PrivateKey *packet.PrivateKey
|
|
||||||
Sig *packet.Signature
|
|
||||||
Revocations []*packet.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Key identifies a specific public key in an Entity. This is either the
|
|
||||||
// Entity's primary key or a subkey.
|
|
||||||
type Key struct {
|
|
||||||
Entity *Entity
|
|
||||||
PublicKey *packet.PublicKey
|
|
||||||
PrivateKey *packet.PrivateKey
|
|
||||||
SelfSignature *packet.Signature
|
|
||||||
Revocations []*packet.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// A KeyRing provides access to public and private keys.
|
|
||||||
type KeyRing interface {
|
|
||||||
// KeysById returns the set of keys that have the given key id.
|
|
||||||
KeysById(id uint64) []Key
|
|
||||||
// KeysByIdAndUsage returns the set of keys with the given id
|
|
||||||
// that also meet the key usage given by requiredUsage.
|
|
||||||
// The requiredUsage is expressed as the bitwise-OR of
|
|
||||||
// packet.KeyFlag* values.
|
|
||||||
KeysByIdUsage(id uint64, requiredUsage byte) []Key
|
|
||||||
// DecryptionKeys returns all private keys that are valid for
|
|
||||||
// decryption.
|
|
||||||
DecryptionKeys() []Key
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrimaryIdentity returns an Identity, preferring non-revoked identities,
|
|
||||||
// identities marked as primary, or the latest-created identity, in that order.
|
|
||||||
func (e *Entity) PrimaryIdentity() *Identity {
|
|
||||||
var primaryIdentity *Identity
|
|
||||||
for _, ident := range e.Identities {
|
|
||||||
if shouldPreferIdentity(primaryIdentity, ident) {
|
|
||||||
primaryIdentity = ident
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return primaryIdentity
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldPreferIdentity(existingId, potentialNewId *Identity) bool {
|
|
||||||
if existingId == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(existingId.Revocations) > len(potentialNewId.Revocations) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(existingId.Revocations) < len(potentialNewId.Revocations) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if existingId.SelfSignature == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId &&
|
|
||||||
!(potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId) &&
|
|
||||||
potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialNewId.SelfSignature.CreationTime.After(existingId.SelfSignature.CreationTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptionKey returns the best candidate Key for encrypting a message to the
|
|
||||||
// given Entity.
|
|
||||||
func (e *Entity) EncryptionKey(now time.Time) (Key, bool) {
|
|
||||||
// Fail to find any encryption key if the...
|
|
||||||
i := e.PrimaryIdentity()
|
|
||||||
if e.PrimaryKey.KeyExpired(i.SelfSignature, now) || // primary key has expired
|
|
||||||
i.SelfSignature == nil || // user ID has no self-signature
|
|
||||||
i.SelfSignature.SigExpired(now) || // user ID self-signature has expired
|
|
||||||
e.Revoked(now) || // primary key has been revoked
|
|
||||||
i.Revoked(now) { // user ID has been revoked
|
|
||||||
return Key{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate the keys to find the newest, unexpired one
|
|
||||||
candidateSubkey := -1
|
|
||||||
var maxTime time.Time
|
|
||||||
for i, subkey := range e.Subkeys {
|
|
||||||
if subkey.Sig.FlagsValid &&
|
|
||||||
subkey.Sig.FlagEncryptCommunications &&
|
|
||||||
subkey.PublicKey.PubKeyAlgo.CanEncrypt() &&
|
|
||||||
!subkey.PublicKey.KeyExpired(subkey.Sig, now) &&
|
|
||||||
!subkey.Sig.SigExpired(now) &&
|
|
||||||
!subkey.Revoked(now) &&
|
|
||||||
(maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) {
|
|
||||||
candidateSubkey = i
|
|
||||||
maxTime = subkey.Sig.CreationTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if candidateSubkey != -1 {
|
|
||||||
subkey := e.Subkeys[candidateSubkey]
|
|
||||||
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have any subkeys for encryption and the primary key
|
|
||||||
// is marked as OK to encrypt with, then we can use it.
|
|
||||||
if i.SelfSignature.FlagsValid && i.SelfSignature.FlagEncryptCommunications &&
|
|
||||||
e.PrimaryKey.PubKeyAlgo.CanEncrypt() {
|
|
||||||
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return Key{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertificationKey return the best candidate Key for certifying a key with this
|
|
||||||
// Entity.
|
|
||||||
func (e *Entity) CertificationKey(now time.Time) (Key, bool) {
|
|
||||||
return e.CertificationKeyById(now, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertificationKeyById return the Key for key certification with this
|
|
||||||
// Entity and keyID.
|
|
||||||
func (e *Entity) CertificationKeyById(now time.Time, id uint64) (Key, bool) {
|
|
||||||
return e.signingKeyByIdUsage(now, id, packet.KeyFlagCertify)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigningKey return the best candidate Key for signing a message with this
|
|
||||||
// Entity.
|
|
||||||
func (e *Entity) SigningKey(now time.Time) (Key, bool) {
|
|
||||||
return e.SigningKeyById(now, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigningKeyById return the Key for signing a message with this
|
|
||||||
// Entity and keyID.
|
|
||||||
func (e *Entity) SigningKeyById(now time.Time, id uint64) (Key, bool) {
|
|
||||||
return e.signingKeyByIdUsage(now, id, packet.KeyFlagSign)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, bool) {
|
|
||||||
// Fail to find any signing key if the...
|
|
||||||
i := e.PrimaryIdentity()
|
|
||||||
if e.PrimaryKey.KeyExpired(i.SelfSignature, now) || // primary key has expired
|
|
||||||
i.SelfSignature == nil || // user ID has no self-signature
|
|
||||||
i.SelfSignature.SigExpired(now) || // user ID self-signature has expired
|
|
||||||
e.Revoked(now) || // primary key has been revoked
|
|
||||||
i.Revoked(now) { // user ID has been revoked
|
|
||||||
return Key{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate the keys to find the newest, unexpired one
|
|
||||||
candidateSubkey := -1
|
|
||||||
var maxTime time.Time
|
|
||||||
for idx, subkey := range e.Subkeys {
|
|
||||||
if subkey.Sig.FlagsValid &&
|
|
||||||
(flags&packet.KeyFlagCertify == 0 || subkey.Sig.FlagCertify) &&
|
|
||||||
(flags&packet.KeyFlagSign == 0 || subkey.Sig.FlagSign) &&
|
|
||||||
subkey.PublicKey.PubKeyAlgo.CanSign() &&
|
|
||||||
!subkey.PublicKey.KeyExpired(subkey.Sig, now) &&
|
|
||||||
!subkey.Sig.SigExpired(now) &&
|
|
||||||
!subkey.Revoked(now) &&
|
|
||||||
(maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) &&
|
|
||||||
(id == 0 || subkey.PublicKey.KeyId == id) {
|
|
||||||
candidateSubkey = idx
|
|
||||||
maxTime = subkey.Sig.CreationTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if candidateSubkey != -1 {
|
|
||||||
subkey := e.Subkeys[candidateSubkey]
|
|
||||||
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have any subkeys for signing and the primary key
|
|
||||||
// is marked as OK to sign with, then we can use it.
|
|
||||||
if i.SelfSignature.FlagsValid &&
|
|
||||||
(flags&packet.KeyFlagCertify == 0 || i.SelfSignature.FlagCertify) &&
|
|
||||||
(flags&packet.KeyFlagSign == 0 || i.SelfSignature.FlagSign) &&
|
|
||||||
e.PrimaryKey.PubKeyAlgo.CanSign() &&
|
|
||||||
(id == 0 || e.PrimaryKey.KeyId == id) {
|
|
||||||
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// No keys with a valid Signing Flag or no keys matched the id passed in
|
|
||||||
return Key{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func revoked(revocations []*packet.Signature, now time.Time) bool {
|
|
||||||
for _, revocation := range revocations {
|
|
||||||
if revocation.RevocationReason != nil && *revocation.RevocationReason == packet.KeyCompromised {
|
|
||||||
// If the key is compromised, the key is considered revoked even before the revocation date.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !revocation.SigExpired(now) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoked returns whether the entity has any direct key revocation signatures.
|
|
||||||
// Note that third-party revocation signatures are not supported.
|
|
||||||
// Note also that Identity and Subkey revocation should be checked separately.
|
|
||||||
func (e *Entity) Revoked(now time.Time) bool {
|
|
||||||
return revoked(e.Revocations, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptPrivateKeys encrypts all non-encrypted keys in the entity with the same key
|
|
||||||
// derived from the provided passphrase. Public keys and dummy keys are ignored,
|
|
||||||
// and don't cause an error to be returned.
|
|
||||||
func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) error {
|
|
||||||
var keysToEncrypt []*packet.PrivateKey
|
|
||||||
// Add entity private key to encrypt.
|
|
||||||
if e.PrivateKey != nil && !e.PrivateKey.Dummy() && !e.PrivateKey.Encrypted {
|
|
||||||
keysToEncrypt = append(keysToEncrypt, e.PrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add subkeys to encrypt.
|
|
||||||
for _, sub := range e.Subkeys {
|
|
||||||
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && !sub.PrivateKey.Encrypted {
|
|
||||||
keysToEncrypt = append(keysToEncrypt, sub.PrivateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return packet.EncryptPrivateKeys(keysToEncrypt, passphrase, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptPrivateKeys decrypts all encrypted keys in the entitiy with the given passphrase.
|
|
||||||
// Avoids recomputation of similar s2k key derivations. Public keys and dummy keys are ignored,
|
|
||||||
// and don't cause an error to be returned.
|
|
||||||
func (e *Entity) DecryptPrivateKeys(passphrase []byte) error {
|
|
||||||
var keysToDecrypt []*packet.PrivateKey
|
|
||||||
// Add entity private key to decrypt.
|
|
||||||
if e.PrivateKey != nil && !e.PrivateKey.Dummy() && e.PrivateKey.Encrypted {
|
|
||||||
keysToDecrypt = append(keysToDecrypt, e.PrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add subkeys to decrypt.
|
|
||||||
for _, sub := range e.Subkeys {
|
|
||||||
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted {
|
|
||||||
keysToDecrypt = append(keysToDecrypt, sub.PrivateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return packet.DecryptPrivateKeys(keysToDecrypt, passphrase)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoked returns whether the identity has been revoked by a self-signature.
|
|
||||||
// Note that third-party revocation signatures are not supported.
|
|
||||||
func (i *Identity) Revoked(now time.Time) bool {
|
|
||||||
return revoked(i.Revocations, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoked returns whether the subkey has been revoked by a self-signature.
|
|
||||||
// Note that third-party revocation signatures are not supported.
|
|
||||||
func (s *Subkey) Revoked(now time.Time) bool {
|
|
||||||
return revoked(s.Revocations, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoked returns whether the key or subkey has been revoked by a self-signature.
|
|
||||||
// Note that third-party revocation signatures are not supported.
|
|
||||||
// Note also that Identity revocation should be checked separately.
|
|
||||||
// Normally, it's not necessary to call this function, except on keys returned by
|
|
||||||
// KeysById or KeysByIdUsage.
|
|
||||||
func (key *Key) Revoked(now time.Time) bool {
|
|
||||||
return revoked(key.Revocations, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An EntityList contains one or more Entities.
|
|
||||||
type EntityList []*Entity
|
|
||||||
|
|
||||||
// KeysById returns the set of keys that have the given key id.
|
|
||||||
func (el EntityList) KeysById(id uint64) (keys []Key) {
|
|
||||||
for _, e := range el {
|
|
||||||
if e.PrimaryKey.KeyId == id {
|
|
||||||
ident := e.PrimaryIdentity()
|
|
||||||
selfSig := ident.SelfSignature
|
|
||||||
keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig, e.Revocations})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, subKey := range e.Subkeys {
|
|
||||||
if subKey.PublicKey.KeyId == id {
|
|
||||||
keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig, subKey.Revocations})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeysByIdAndUsage returns the set of keys with the given id that also meet
|
|
||||||
// the key usage given by requiredUsage. The requiredUsage is expressed as
|
|
||||||
// the bitwise-OR of packet.KeyFlag* values.
|
|
||||||
func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
|
|
||||||
for _, key := range el.KeysById(id) {
|
|
||||||
if requiredUsage != 0 {
|
|
||||||
if key.SelfSignature == nil || !key.SelfSignature.FlagsValid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var usage byte
|
|
||||||
if key.SelfSignature.FlagCertify {
|
|
||||||
usage |= packet.KeyFlagCertify
|
|
||||||
}
|
|
||||||
if key.SelfSignature.FlagSign {
|
|
||||||
usage |= packet.KeyFlagSign
|
|
||||||
}
|
|
||||||
if key.SelfSignature.FlagEncryptCommunications {
|
|
||||||
usage |= packet.KeyFlagEncryptCommunications
|
|
||||||
}
|
|
||||||
if key.SelfSignature.FlagEncryptStorage {
|
|
||||||
usage |= packet.KeyFlagEncryptStorage
|
|
||||||
}
|
|
||||||
if usage&requiredUsage != requiredUsage {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptionKeys returns all private keys that are valid for decryption.
|
|
||||||
func (el EntityList) DecryptionKeys() (keys []Key) {
|
|
||||||
for _, e := range el {
|
|
||||||
for _, subKey := range e.Subkeys {
|
|
||||||
if subKey.PrivateKey != nil && subKey.Sig.FlagsValid && (subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
|
|
||||||
keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig, subKey.Revocations})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file.
|
|
||||||
func ReadArmoredKeyRing(r io.Reader) (EntityList, error) {
|
|
||||||
block, err := armor.Decode(r)
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil, errors.InvalidArgumentError("no armored data found")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if block.Type != PublicKeyType && block.Type != PrivateKeyType {
|
|
||||||
return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReadKeyRing(block.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadKeyRing reads one or more public/private keys. Unsupported keys are
|
|
||||||
// ignored as long as at least a single valid key is found.
|
|
||||||
func ReadKeyRing(r io.Reader) (el EntityList, err error) {
|
|
||||||
packets := packet.NewReader(r)
|
|
||||||
var lastUnsupportedError error
|
|
||||||
|
|
||||||
for {
|
|
||||||
var e *Entity
|
|
||||||
e, err = ReadEntity(packets)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: warn about skipped unsupported/unreadable keys
|
|
||||||
if _, ok := err.(errors.UnsupportedError); ok {
|
|
||||||
lastUnsupportedError = err
|
|
||||||
err = readToNextPublicKey(packets)
|
|
||||||
} else if _, ok := err.(errors.StructuralError); ok {
|
|
||||||
// Skip unreadable, badly-formatted keys
|
|
||||||
lastUnsupportedError = err
|
|
||||||
err = readToNextPublicKey(packets)
|
|
||||||
}
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
el = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
el = append(el, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(el) == 0 && err == nil {
|
|
||||||
err = lastUnsupportedError
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// readToNextPublicKey reads packets until the start of the entity and leaves
|
|
||||||
// the first packet of the new entity in the Reader.
|
|
||||||
func readToNextPublicKey(packets *packet.Reader) (err error) {
|
|
||||||
var p packet.Packet
|
|
||||||
for {
|
|
||||||
p, err = packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
if _, ok := err.(errors.UnsupportedError); ok {
|
|
||||||
err = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey {
|
|
||||||
packets.Unread(p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadEntity reads an entity (public key, identities, subkeys etc) from the
|
|
||||||
// given Reader.
|
|
||||||
func ReadEntity(packets *packet.Reader) (*Entity, error) {
|
|
||||||
e := new(Entity)
|
|
||||||
e.Identities = make(map[string]*Identity)
|
|
||||||
|
|
||||||
p, err := packets.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
|
|
||||||
if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
|
|
||||||
packets.Unread(p)
|
|
||||||
return nil, errors.StructuralError("first packet was not a public/private key")
|
|
||||||
}
|
|
||||||
e.PrimaryKey = &e.PrivateKey.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
if !e.PrimaryKey.PubKeyAlgo.CanSign() {
|
|
||||||
return nil, errors.StructuralError("primary key cannot be used for signatures")
|
|
||||||
}
|
|
||||||
|
|
||||||
var revocations []*packet.Signature
|
|
||||||
EachPacket:
|
|
||||||
for {
|
|
||||||
p, err := packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pkt := p.(type) {
|
|
||||||
case *packet.UserId:
|
|
||||||
if err := addUserID(e, packets, pkt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *packet.Signature:
|
|
||||||
if pkt.SigType == packet.SigTypeKeyRevocation {
|
|
||||||
revocations = append(revocations, pkt)
|
|
||||||
} else if pkt.SigType == packet.SigTypeDirectSignature {
|
|
||||||
// TODO: RFC4880 5.2.1 permits signatures
|
|
||||||
// directly on keys (eg. to bind additional
|
|
||||||
// revocation keys).
|
|
||||||
}
|
|
||||||
// Else, ignoring the signature as it does not follow anything
|
|
||||||
// we would know to attach it to.
|
|
||||||
case *packet.PrivateKey:
|
|
||||||
if !pkt.IsSubkey {
|
|
||||||
packets.Unread(p)
|
|
||||||
break EachPacket
|
|
||||||
}
|
|
||||||
err = addSubkey(e, packets, &pkt.PublicKey, pkt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *packet.PublicKey:
|
|
||||||
if !pkt.IsSubkey {
|
|
||||||
packets.Unread(p)
|
|
||||||
break EachPacket
|
|
||||||
}
|
|
||||||
err = addSubkey(e, packets, pkt, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// we ignore unknown packets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(e.Identities) == 0 {
|
|
||||||
return nil, errors.StructuralError("entity without any identities")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, revocation := range revocations {
|
|
||||||
err = e.PrimaryKey.VerifyRevocationSignature(revocation)
|
|
||||||
if err == nil {
|
|
||||||
e.Revocations = append(e.Revocations, revocation)
|
|
||||||
} else {
|
|
||||||
// TODO: RFC 4880 5.2.3.15 defines revocation keys.
|
|
||||||
return nil, errors.StructuralError("revocation signature signed by alternate key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addUserID(e *Entity, packets *packet.Reader, pkt *packet.UserId) error {
|
|
||||||
// Make a new Identity object, that we might wind up throwing away.
|
|
||||||
// We'll only add it if we get a valid self-signature over this
|
|
||||||
// userID.
|
|
||||||
identity := new(Identity)
|
|
||||||
identity.Name = pkt.Id
|
|
||||||
identity.UserId = pkt
|
|
||||||
|
|
||||||
for {
|
|
||||||
p, err := packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, ok := p.(*packet.Signature)
|
|
||||||
if !ok {
|
|
||||||
packets.Unread(p)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig.SigType != packet.SigTypeGenericCert &&
|
|
||||||
sig.SigType != packet.SigTypePersonaCert &&
|
|
||||||
sig.SigType != packet.SigTypeCasualCert &&
|
|
||||||
sig.SigType != packet.SigTypePositiveCert &&
|
|
||||||
sig.SigType != packet.SigTypeCertificationRevocation {
|
|
||||||
return errors.StructuralError("user ID signature with wrong type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig.CheckKeyIdOrFingerprint(e.PrimaryKey) {
|
|
||||||
if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, e.PrimaryKey, sig); err != nil {
|
|
||||||
return errors.StructuralError("user ID self-signature invalid: " + err.Error())
|
|
||||||
}
|
|
||||||
if sig.SigType == packet.SigTypeCertificationRevocation {
|
|
||||||
identity.Revocations = append(identity.Revocations, sig)
|
|
||||||
} else if identity.SelfSignature == nil || sig.CreationTime.After(identity.SelfSignature.CreationTime) {
|
|
||||||
identity.SelfSignature = sig
|
|
||||||
}
|
|
||||||
identity.Signatures = append(identity.Signatures, sig)
|
|
||||||
e.Identities[pkt.Id] = identity
|
|
||||||
} else {
|
|
||||||
identity.Signatures = append(identity.Signatures, sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error {
|
|
||||||
var subKey Subkey
|
|
||||||
subKey.PublicKey = pub
|
|
||||||
subKey.PrivateKey = priv
|
|
||||||
|
|
||||||
for {
|
|
||||||
p, err := packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return errors.StructuralError("subkey signature invalid: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, ok := p.(*packet.Signature)
|
|
||||||
if !ok {
|
|
||||||
packets.Unread(p)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig.SigType != packet.SigTypeSubkeyBinding && sig.SigType != packet.SigTypeSubkeyRevocation {
|
|
||||||
return errors.StructuralError("subkey signature with wrong type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, sig); err != nil {
|
|
||||||
return errors.StructuralError("subkey signature invalid: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sig.SigType {
|
|
||||||
case packet.SigTypeSubkeyRevocation:
|
|
||||||
subKey.Revocations = append(subKey.Revocations, sig)
|
|
||||||
case packet.SigTypeSubkeyBinding:
|
|
||||||
if subKey.Sig == nil || sig.CreationTime.After(subKey.Sig.CreationTime) {
|
|
||||||
subKey.Sig = sig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if subKey.Sig == nil {
|
|
||||||
return errors.StructuralError("subkey packet not followed by signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Subkeys = append(e.Subkeys, subKey)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializePrivate serializes an Entity, including private key material, but
|
|
||||||
// excluding signatures from other entities, to the given Writer.
|
|
||||||
// Identities and subkeys are re-signed in case they changed since NewEntry.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *Entity) SerializePrivate(w io.Writer, config *packet.Config) (err error) {
|
|
||||||
if e.PrivateKey.Dummy() {
|
|
||||||
return errors.ErrDummyPrivateKey("dummy private key cannot re-sign identities")
|
|
||||||
}
|
|
||||||
return e.serializePrivate(w, config, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializePrivateWithoutSigning serializes an Entity, including private key
|
|
||||||
// material, but excluding signatures from other entities, to the given Writer.
|
|
||||||
// Self-signatures of identities and subkeys are not re-signed. This is useful
|
|
||||||
// when serializing GNU dummy keys, among other things.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *Entity) SerializePrivateWithoutSigning(w io.Writer, config *packet.Config) (err error) {
|
|
||||||
return e.serializePrivate(w, config, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entity) serializePrivate(w io.Writer, config *packet.Config, reSign bool) (err error) {
|
|
||||||
if e.PrivateKey == nil {
|
|
||||||
return goerrors.New("openpgp: private key is missing")
|
|
||||||
}
|
|
||||||
err = e.PrivateKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, revocation := range e.Revocations {
|
|
||||||
err := revocation.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ident := range e.Identities {
|
|
||||||
err = ident.UserId.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if reSign {
|
|
||||||
if ident.SelfSignature == nil {
|
|
||||||
return goerrors.New("openpgp: can't re-sign identity without valid self-signature")
|
|
||||||
}
|
|
||||||
err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, sig := range ident.Signatures {
|
|
||||||
err = sig.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, subkey := range e.Subkeys {
|
|
||||||
err = subkey.PrivateKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if reSign {
|
|
||||||
err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if subkey.Sig.EmbeddedSignature != nil {
|
|
||||||
err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey,
|
|
||||||
subkey.PrivateKey, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, revocation := range subkey.Revocations {
|
|
||||||
err := revocation.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = subkey.Sig.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize writes the public part of the given Entity to w, including
|
|
||||||
// signatures from other entities. No private key material will be output.
|
|
||||||
func (e *Entity) Serialize(w io.Writer) error {
|
|
||||||
err := e.PrimaryKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, revocation := range e.Revocations {
|
|
||||||
err := revocation.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ident := range e.Identities {
|
|
||||||
err = ident.UserId.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, sig := range ident.Signatures {
|
|
||||||
err = sig.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, subkey := range e.Subkeys {
|
|
||||||
err = subkey.PublicKey.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, revocation := range subkey.Revocations {
|
|
||||||
err := revocation.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = subkey.Sig.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignIdentity adds a signature to e, from signer, attesting that identity is
|
|
||||||
// associated with e. The provided identity must already be an element of
|
|
||||||
// e.Identities and the private key of signer must have been decrypted if
|
|
||||||
// necessary.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Config) error {
|
|
||||||
certificationKey, ok := signer.CertificationKey(config.Now())
|
|
||||||
if !ok {
|
|
||||||
return errors.InvalidArgumentError("no valid certification key found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if certificationKey.PrivateKey.Encrypted {
|
|
||||||
return errors.InvalidArgumentError("signing Entity's private key must be decrypted")
|
|
||||||
}
|
|
||||||
|
|
||||||
ident, ok := e.Identities[identity]
|
|
||||||
if !ok {
|
|
||||||
return errors.InvalidArgumentError("given identity string not found in Entity")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := createSignaturePacket(certificationKey.PublicKey, packet.SigTypeGenericCert, config)
|
|
||||||
|
|
||||||
signingUserID := config.SigningUserId()
|
|
||||||
if signingUserID != "" {
|
|
||||||
if _, ok := signer.Identities[signingUserID]; !ok {
|
|
||||||
return errors.InvalidArgumentError("signer identity string not found in signer Entity")
|
|
||||||
}
|
|
||||||
sig.SignerUserId = &signingUserID
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sig.SignUserId(identity, e.PrimaryKey, certificationKey.PrivateKey, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ident.Signatures = append(ident.Signatures, sig)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevokeKey generates a key revocation signature (packet.SigTypeKeyRevocation) with the
|
|
||||||
// specified reason code and text (RFC4880 section-5.2.3.23).
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *Entity) RevokeKey(reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error {
|
|
||||||
revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeKeyRevocation, config)
|
|
||||||
revSig.RevocationReason = &reason
|
|
||||||
revSig.RevocationReasonText = reasonText
|
|
||||||
|
|
||||||
if err := revSig.RevokeKey(e.PrimaryKey, e.PrivateKey, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.Revocations = append(e.Revocations, revSig)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevokeSubkey generates a subkey revocation signature (packet.SigTypeSubkeyRevocation) for
|
|
||||||
// a subkey with the specified reason code and text (RFC4880 section-5.2.3.23).
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *Entity) RevokeSubkey(sk *Subkey, reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error {
|
|
||||||
if err := e.PrimaryKey.VerifyKeySignature(sk.PublicKey, sk.Sig); err != nil {
|
|
||||||
return errors.InvalidArgumentError("given subkey is not associated with this key")
|
|
||||||
}
|
|
||||||
|
|
||||||
revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyRevocation, config)
|
|
||||||
revSig.RevocationReason = &reason
|
|
||||||
revSig.RevocationReasonText = reasonText
|
|
||||||
|
|
||||||
if err := revSig.RevokeSubkey(sk.PublicKey, e.PrivateKey, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sk.Revocations = append(sk.Revocations, revSig)
|
|
||||||
return nil
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@ -1,67 +0,0 @@
|
|||||||
// Copyright (C) 2019 ProtonTech AG
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import "math/bits"
|
|
||||||
|
|
||||||
// CipherSuite contains a combination of Cipher and Mode
|
|
||||||
type CipherSuite struct {
|
|
||||||
// The cipher function
|
|
||||||
Cipher CipherFunction
|
|
||||||
// The AEAD mode of operation.
|
|
||||||
Mode AEADMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// AEADConfig collects a number of AEAD parameters along with sensible defaults.
|
|
||||||
// A nil AEADConfig is valid and results in all default values.
|
|
||||||
type AEADConfig struct {
|
|
||||||
// The AEAD mode of operation.
|
|
||||||
DefaultMode AEADMode
|
|
||||||
// Amount of octets in each chunk of data
|
|
||||||
ChunkSize uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the AEAD mode of operation.
|
|
||||||
func (conf *AEADConfig) Mode() AEADMode {
|
|
||||||
// If no preference is specified, OCB is used (which is mandatory to implement).
|
|
||||||
if conf == nil || conf.DefaultMode == 0 {
|
|
||||||
return AEADModeOCB
|
|
||||||
}
|
|
||||||
|
|
||||||
mode := conf.DefaultMode
|
|
||||||
if mode != AEADModeEAX && mode != AEADModeOCB && mode != AEADModeGCM {
|
|
||||||
panic("AEAD mode unsupported")
|
|
||||||
}
|
|
||||||
return mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChunkSizeByte returns the byte indicating the chunk size. The effective
|
|
||||||
// chunk size is computed with the formula uint64(1) << (chunkSizeByte + 6)
|
|
||||||
// limit to 16 = 4 MiB
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
|
||||||
func (conf *AEADConfig) ChunkSizeByte() byte {
|
|
||||||
if conf == nil || conf.ChunkSize == 0 {
|
|
||||||
return 12 // 1 << (12 + 6) == 262144 bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
chunkSize := conf.ChunkSize
|
|
||||||
exponent := bits.Len64(chunkSize) - 1
|
|
||||||
switch {
|
|
||||||
case exponent < 6:
|
|
||||||
exponent = 6
|
|
||||||
case exponent > 16:
|
|
||||||
exponent = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
return byte(exponent - 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeAEADChunkSize returns the effective chunk size. In 32-bit systems, the
|
|
||||||
// maximum returned value is 1 << 30.
|
|
||||||
func decodeAEADChunkSize(c byte) int {
|
|
||||||
size := uint64(1 << (c + 6))
|
|
||||||
if size != uint64(int(size)) {
|
|
||||||
return 1 << 30
|
|
||||||
}
|
|
||||||
return int(size)
|
|
||||||
}
|
|
@ -1,264 +0,0 @@
|
|||||||
// Copyright (C) 2019 ProtonTech AG
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// aeadCrypter is an AEAD opener/sealer, its configuration, and data for en/decryption.
|
|
||||||
type aeadCrypter struct {
|
|
||||||
aead cipher.AEAD
|
|
||||||
chunkSize int
|
|
||||||
initialNonce []byte
|
|
||||||
associatedData []byte // Chunk-independent associated data
|
|
||||||
chunkIndex []byte // Chunk counter
|
|
||||||
packetTag packetType // SEIP packet (v2) or AEAD Encrypted Data packet
|
|
||||||
bytesProcessed int // Amount of plaintext bytes encrypted/decrypted
|
|
||||||
buffer bytes.Buffer // Buffered bytes across chunks
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeNonce takes the incremental index and computes an eXclusive OR with
|
|
||||||
// the least significant 8 bytes of the receivers' initial nonce (see sec.
|
|
||||||
// 5.16.1 and 5.16.2). It returns the resulting nonce.
|
|
||||||
func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
|
|
||||||
if wo.packetTag == packetTypeSymmetricallyEncryptedIntegrityProtected {
|
|
||||||
return append(wo.initialNonce, wo.chunkIndex...)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce = make([]byte, len(wo.initialNonce))
|
|
||||||
copy(nonce, wo.initialNonce)
|
|
||||||
offset := len(wo.initialNonce) - 8
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
nonce[i+offset] ^= wo.chunkIndex[i]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// incrementIndex performs an integer increment by 1 of the integer represented by the
|
|
||||||
// slice, modifying it accordingly.
|
|
||||||
func (wo *aeadCrypter) incrementIndex() error {
|
|
||||||
index := wo.chunkIndex
|
|
||||||
if len(index) == 0 {
|
|
||||||
return errors.AEADError("Index has length 0")
|
|
||||||
}
|
|
||||||
for i := len(index) - 1; i >= 0; i-- {
|
|
||||||
if index[i] < 255 {
|
|
||||||
index[i]++
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
index[i] = 0
|
|
||||||
}
|
|
||||||
return errors.AEADError("cannot further increment index")
|
|
||||||
}
|
|
||||||
|
|
||||||
// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
|
|
||||||
// necessary, similar to aeadEncrypter.
|
|
||||||
type aeadDecrypter struct {
|
|
||||||
aeadCrypter // Embedded ciphertext opener
|
|
||||||
reader io.Reader // 'reader' is a partialLengthReader
|
|
||||||
peekedBytes []byte // Used to detect last chunk
|
|
||||||
eof bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read decrypts bytes and reads them into dst. It decrypts when necessary and
|
|
||||||
// buffers extra decrypted bytes. It returns the number of bytes copied into dst
|
|
||||||
// and an error.
|
|
||||||
func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) {
|
|
||||||
// Return buffered plaintext bytes from previous calls
|
|
||||||
if ar.buffer.Len() > 0 {
|
|
||||||
return ar.buffer.Read(dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return EOF if we've previously validated the final tag
|
|
||||||
if ar.eof {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read a chunk
|
|
||||||
tagLen := ar.aead.Overhead()
|
|
||||||
cipherChunkBuf := new(bytes.Buffer)
|
|
||||||
_, errRead := io.CopyN(cipherChunkBuf, ar.reader, int64(ar.chunkSize+tagLen))
|
|
||||||
cipherChunk := cipherChunkBuf.Bytes()
|
|
||||||
if errRead != nil && errRead != io.EOF {
|
|
||||||
return 0, errRead
|
|
||||||
}
|
|
||||||
decrypted, errChunk := ar.openChunk(cipherChunk)
|
|
||||||
if errChunk != nil {
|
|
||||||
return 0, errChunk
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return decrypted bytes, buffering if necessary
|
|
||||||
if len(dst) < len(decrypted) {
|
|
||||||
n = copy(dst, decrypted[:len(dst)])
|
|
||||||
ar.buffer.Write(decrypted[len(dst):])
|
|
||||||
} else {
|
|
||||||
n = copy(dst, decrypted)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check final authentication tag
|
|
||||||
if errRead == io.EOF {
|
|
||||||
errChunk := ar.validateFinalTag(ar.peekedBytes)
|
|
||||||
if errChunk != nil {
|
|
||||||
return n, errChunk
|
|
||||||
}
|
|
||||||
ar.eof = true // Mark EOF for when we've returned all buffered data
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is noOp. The final authentication tag of the stream was already
|
|
||||||
// checked in the last Read call. In the future, this function could be used to
|
|
||||||
// wipe the reader and peeked, decrypted bytes, if necessary.
|
|
||||||
func (ar *aeadDecrypter) Close() (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// openChunk decrypts and checks integrity of an encrypted chunk, returning
|
|
||||||
// the underlying plaintext and an error. It accesses peeked bytes from next
|
|
||||||
// chunk, to identify the last chunk and decrypt/validate accordingly.
|
|
||||||
func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) {
|
|
||||||
tagLen := ar.aead.Overhead()
|
|
||||||
// Restore carried bytes from last call
|
|
||||||
chunkExtra := append(ar.peekedBytes, data...)
|
|
||||||
// 'chunk' contains encrypted bytes, followed by an authentication tag.
|
|
||||||
chunk := chunkExtra[:len(chunkExtra)-tagLen]
|
|
||||||
ar.peekedBytes = chunkExtra[len(chunkExtra)-tagLen:]
|
|
||||||
|
|
||||||
adata := ar.associatedData
|
|
||||||
if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
|
||||||
adata = append(ar.associatedData, ar.chunkIndex...)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := ar.computeNextNonce()
|
|
||||||
plainChunk, err := ar.aead.Open(nil, nonce, chunk, adata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ar.bytesProcessed += len(plainChunk)
|
|
||||||
if err = ar.aeadCrypter.incrementIndex(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return plainChunk, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks the summary tag. It takes into account the total decrypted bytes into
|
|
||||||
// the associated data. It returns an error, or nil if the tag is valid.
|
|
||||||
func (ar *aeadDecrypter) validateFinalTag(tag []byte) error {
|
|
||||||
// Associated: tag, version, cipher, aead, chunk size, ...
|
|
||||||
amountBytes := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed))
|
|
||||||
|
|
||||||
adata := ar.associatedData
|
|
||||||
if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
|
||||||
// ... index ...
|
|
||||||
adata = append(ar.associatedData, ar.chunkIndex...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... and total number of encrypted octets
|
|
||||||
adata = append(adata, amountBytes...)
|
|
||||||
nonce := ar.computeNextNonce()
|
|
||||||
_, err := ar.aead.Open(nil, nonce, tag, adata)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
|
|
||||||
// to the AEAD block size, and buffers the extra encrypted bytes for next write.
|
|
||||||
type aeadEncrypter struct {
|
|
||||||
aeadCrypter // Embedded plaintext sealer
|
|
||||||
writer io.WriteCloser // 'writer' is a partialLengthWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write encrypts and writes bytes. It encrypts when necessary and buffers extra
|
|
||||||
// plaintext bytes for next call. When the stream is finished, Close() MUST be
|
|
||||||
// called to append the final tag.
|
|
||||||
func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) {
|
|
||||||
// Append plaintextBytes to existing buffered bytes
|
|
||||||
n, err = aw.buffer.Write(plaintextBytes)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
// Encrypt and write chunks
|
|
||||||
for aw.buffer.Len() >= aw.chunkSize {
|
|
||||||
plainChunk := aw.buffer.Next(aw.chunkSize)
|
|
||||||
encryptedChunk, err := aw.sealChunk(plainChunk)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
_, err = aw.writer.Write(encryptedChunk)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close encrypts and writes the remaining buffered plaintext if any, appends
|
|
||||||
// the final authentication tag, and closes the embedded writer. This function
|
|
||||||
// MUST be called at the end of a stream.
|
|
||||||
func (aw *aeadEncrypter) Close() (err error) {
|
|
||||||
// Encrypt and write a chunk if there's buffered data left, or if we haven't
|
|
||||||
// written any chunks yet.
|
|
||||||
if aw.buffer.Len() > 0 || aw.bytesProcessed == 0 {
|
|
||||||
plainChunk := aw.buffer.Bytes()
|
|
||||||
lastEncryptedChunk, err := aw.sealChunk(plainChunk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = aw.writer.Write(lastEncryptedChunk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Compute final tag (associated data: packet tag, version, cipher, aead,
|
|
||||||
// chunk size...
|
|
||||||
adata := aw.associatedData
|
|
||||||
|
|
||||||
if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
|
||||||
// ... index ...
|
|
||||||
adata = append(aw.associatedData, aw.chunkIndex...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... and total number of encrypted octets
|
|
||||||
amountBytes := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(amountBytes, uint64(aw.bytesProcessed))
|
|
||||||
adata = append(adata, amountBytes...)
|
|
||||||
|
|
||||||
nonce := aw.computeNextNonce()
|
|
||||||
finalTag := aw.aead.Seal(nil, nonce, nil, adata)
|
|
||||||
_, err = aw.writer.Write(finalTag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return aw.writer.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sealChunk Encrypts and authenticates the given chunk.
|
|
||||||
func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) {
|
|
||||||
if len(data) > aw.chunkSize {
|
|
||||||
return nil, errors.AEADError("chunk exceeds maximum length")
|
|
||||||
}
|
|
||||||
if aw.associatedData == nil {
|
|
||||||
return nil, errors.AEADError("can't seal without headers")
|
|
||||||
}
|
|
||||||
adata := aw.associatedData
|
|
||||||
if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
|
|
||||||
adata = append(aw.associatedData, aw.chunkIndex...)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := aw.computeNextNonce()
|
|
||||||
encrypted := aw.aead.Seal(nil, nonce, data, adata)
|
|
||||||
aw.bytesProcessed += len(data)
|
|
||||||
if err := aw.aeadCrypter.incrementIndex(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return encrypted, nil
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
// Copyright (C) 2019 ProtonTech AG
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AEADEncrypted represents an AEAD Encrypted Packet.
|
|
||||||
// See https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
|
|
||||||
type AEADEncrypted struct {
|
|
||||||
cipher CipherFunction
|
|
||||||
mode AEADMode
|
|
||||||
chunkSizeByte byte
|
|
||||||
Contents io.Reader // Encrypted chunks and tags
|
|
||||||
initialNonce []byte // Referred to as IV in RFC4880-bis
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only currently defined version
|
|
||||||
const aeadEncryptedVersion = 1
|
|
||||||
|
|
||||||
func (ae *AEADEncrypted) parse(buf io.Reader) error {
|
|
||||||
headerData := make([]byte, 4)
|
|
||||||
if n, err := io.ReadFull(buf, headerData); n < 4 {
|
|
||||||
return errors.AEADError("could not read aead header:" + err.Error())
|
|
||||||
}
|
|
||||||
// Read initial nonce
|
|
||||||
mode := AEADMode(headerData[2])
|
|
||||||
nonceLen := mode.IvLength()
|
|
||||||
|
|
||||||
// This packet supports only EAX and OCB
|
|
||||||
// https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
|
|
||||||
if nonceLen == 0 || mode > AEADModeOCB {
|
|
||||||
return errors.AEADError("unknown mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
initialNonce := make([]byte, nonceLen)
|
|
||||||
if n, err := io.ReadFull(buf, initialNonce); n < nonceLen {
|
|
||||||
return errors.AEADError("could not read aead nonce:" + err.Error())
|
|
||||||
}
|
|
||||||
ae.Contents = buf
|
|
||||||
ae.initialNonce = initialNonce
|
|
||||||
c := headerData[1]
|
|
||||||
if _, ok := algorithm.CipherById[c]; !ok {
|
|
||||||
return errors.UnsupportedError("unknown cipher: " + string(c))
|
|
||||||
}
|
|
||||||
ae.cipher = CipherFunction(c)
|
|
||||||
ae.mode = mode
|
|
||||||
ae.chunkSizeByte = headerData[3]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt returns a io.ReadCloser from which decrypted bytes can be read, or
|
|
||||||
// an error.
|
|
||||||
func (ae *AEADEncrypted) Decrypt(ciph CipherFunction, key []byte) (io.ReadCloser, error) {
|
|
||||||
return ae.decrypt(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt prepares an aeadCrypter and returns a ReadCloser from which
|
|
||||||
// decrypted bytes can be read (see aeadDecrypter.Read()).
|
|
||||||
func (ae *AEADEncrypted) decrypt(key []byte) (io.ReadCloser, error) {
|
|
||||||
blockCipher := ae.cipher.new(key)
|
|
||||||
aead := ae.mode.new(blockCipher)
|
|
||||||
// Carry the first tagLen bytes
|
|
||||||
tagLen := ae.mode.TagLength()
|
|
||||||
peekedBytes := make([]byte, tagLen)
|
|
||||||
n, err := io.ReadFull(ae.Contents, peekedBytes)
|
|
||||||
if n < tagLen || (err != nil && err != io.EOF) {
|
|
||||||
return nil, errors.AEADError("Not enough data to decrypt:" + err.Error())
|
|
||||||
}
|
|
||||||
chunkSize := decodeAEADChunkSize(ae.chunkSizeByte)
|
|
||||||
return &aeadDecrypter{
|
|
||||||
aeadCrypter: aeadCrypter{
|
|
||||||
aead: aead,
|
|
||||||
chunkSize: chunkSize,
|
|
||||||
initialNonce: ae.initialNonce,
|
|
||||||
associatedData: ae.associatedData(),
|
|
||||||
chunkIndex: make([]byte, 8),
|
|
||||||
packetTag: packetTypeAEADEncrypted,
|
|
||||||
},
|
|
||||||
reader: ae.Contents,
|
|
||||||
peekedBytes: peekedBytes}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// associatedData for chunks: tag, version, cipher, mode, chunk size byte
|
|
||||||
func (ae *AEADEncrypted) associatedData() []byte {
|
|
||||||
return []byte{
|
|
||||||
0xD4,
|
|
||||||
aeadEncryptedVersion,
|
|
||||||
byte(ae.cipher),
|
|
||||||
byte(ae.mode),
|
|
||||||
ae.chunkSizeByte}
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/bzip2"
|
|
||||||
"compress/flate"
|
|
||||||
"compress/zlib"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compressed represents a compressed OpenPGP packet. The decompressed contents
|
|
||||||
// will contain more OpenPGP packets. See RFC 4880, section 5.6.
|
|
||||||
type Compressed struct {
|
|
||||||
Body io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
NoCompression = flate.NoCompression
|
|
||||||
BestSpeed = flate.BestSpeed
|
|
||||||
BestCompression = flate.BestCompression
|
|
||||||
DefaultCompression = flate.DefaultCompression
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompressionConfig contains compressor configuration settings.
|
|
||||||
type CompressionConfig struct {
|
|
||||||
// Level is the compression level to use. It must be set to
|
|
||||||
// between -1 and 9, with -1 causing the compressor to use the
|
|
||||||
// default compression level, 0 causing the compressor to use
|
|
||||||
// no compression and 1 to 9 representing increasing (better,
|
|
||||||
// slower) compression levels. If Level is less than -1 or
|
|
||||||
// more then 9, a non-nil error will be returned during
|
|
||||||
// encryption. See the constants above for convenient common
|
|
||||||
// settings for Level.
|
|
||||||
Level int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compressed) parse(r io.Reader) error {
|
|
||||||
var buf [1]byte
|
|
||||||
_, err := readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch buf[0] {
|
|
||||||
case 0:
|
|
||||||
c.Body = r
|
|
||||||
case 1:
|
|
||||||
c.Body = flate.NewReader(r)
|
|
||||||
case 2:
|
|
||||||
c.Body, err = zlib.NewReader(r)
|
|
||||||
case 3:
|
|
||||||
c.Body = bzip2.NewReader(r)
|
|
||||||
default:
|
|
||||||
err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// compressedWriterCloser represents the serialized compression stream
|
|
||||||
// header and the compressor. Its Close() method ensures that both the
|
|
||||||
// compressor and serialized stream header are closed. Its Write()
|
|
||||||
// method writes to the compressor.
|
|
||||||
type compressedWriteCloser struct {
|
|
||||||
sh io.Closer // Stream Header
|
|
||||||
c io.WriteCloser // Compressor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cwc compressedWriteCloser) Write(p []byte) (int, error) {
|
|
||||||
return cwc.c.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cwc compressedWriteCloser) Close() (err error) {
|
|
||||||
err = cwc.c.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cwc.sh.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeCompressed serializes a compressed data packet to w and
|
|
||||||
// returns a WriteCloser to which the literal data packets themselves
|
|
||||||
// can be written and which MUST be closed on completion. If cc is
|
|
||||||
// nil, sensible defaults will be used to configure the compression
|
|
||||||
// algorithm.
|
|
||||||
func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) {
|
|
||||||
compressed, err := serializeStreamHeader(w, packetTypeCompressed)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = compressed.Write([]byte{uint8(algo)})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
level := DefaultCompression
|
|
||||||
if cc != nil {
|
|
||||||
level = cc.Level
|
|
||||||
}
|
|
||||||
|
|
||||||
var compressor io.WriteCloser
|
|
||||||
switch algo {
|
|
||||||
case CompressionZIP:
|
|
||||||
compressor, err = flate.NewWriter(compressed, level)
|
|
||||||
case CompressionZLIB:
|
|
||||||
compressor, err = zlib.NewWriterLevel(compressed, level)
|
|
||||||
default:
|
|
||||||
s := strconv.Itoa(int(algo))
|
|
||||||
err = errors.UnsupportedError("Unsupported compression algorithm: " + s)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
literaldata = compressedWriteCloser{compressed, compressor}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,248 +0,0 @@
|
|||||||
// 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config collects a number of parameters along with sensible defaults.
|
|
||||||
// A nil *Config is valid and results in all default values.
|
|
||||||
type Config struct {
|
|
||||||
// Rand provides the source of entropy.
|
|
||||||
// If nil, the crypto/rand Reader is used.
|
|
||||||
Rand io.Reader
|
|
||||||
// DefaultHash is the default hash function to be used.
|
|
||||||
// If zero, SHA-256 is used.
|
|
||||||
DefaultHash crypto.Hash
|
|
||||||
// DefaultCipher is the cipher to be used.
|
|
||||||
// If zero, AES-128 is used.
|
|
||||||
DefaultCipher CipherFunction
|
|
||||||
// Time returns the current time as the number of seconds since the
|
|
||||||
// epoch. If Time is nil, time.Now is used.
|
|
||||||
Time func() time.Time
|
|
||||||
// DefaultCompressionAlgo is the compression algorithm to be
|
|
||||||
// applied to the plaintext before encryption. If zero, no
|
|
||||||
// compression is done.
|
|
||||||
DefaultCompressionAlgo CompressionAlgo
|
|
||||||
// CompressionConfig configures the compression settings.
|
|
||||||
CompressionConfig *CompressionConfig
|
|
||||||
// S2K (String to Key) config, used for key derivation in the context of secret key encryption
|
|
||||||
// and password-encrypted data.
|
|
||||||
// If nil, the default configuration is used
|
|
||||||
S2KConfig *s2k.Config
|
|
||||||
// Iteration count for Iterated S2K (String to Key).
|
|
||||||
// Only used if sk2.Mode is nil.
|
|
||||||
// This value is duplicated here from s2k.Config for backwards compatibility.
|
|
||||||
// It determines the strength of the passphrase stretching when
|
|
||||||
// the said passphrase is hashed to produce a key. S2KCount
|
|
||||||
// should be between 65536 and 65011712, inclusive. If Config
|
|
||||||
// is nil or S2KCount is 0, the value 16777216 used. Not all
|
|
||||||
// values in the above range can be represented. S2KCount will
|
|
||||||
// be rounded up to the next representable value if it cannot
|
|
||||||
// be encoded exactly. When set, it is strongly encrouraged to
|
|
||||||
// use a value that is at least 65536. See RFC 4880 Section
|
|
||||||
// 3.7.1.3.
|
|
||||||
//
|
|
||||||
// Deprecated: SK2Count should be configured in S2KConfig instead.
|
|
||||||
S2KCount int
|
|
||||||
// RSABits is the number of bits in new RSA keys made with NewEntity.
|
|
||||||
// If zero, then 2048 bit keys are created.
|
|
||||||
RSABits int
|
|
||||||
// The public key algorithm to use - will always create a signing primary
|
|
||||||
// key and encryption subkey.
|
|
||||||
Algorithm PublicKeyAlgorithm
|
|
||||||
// Some known primes that are optionally prepopulated by the caller
|
|
||||||
RSAPrimes []*big.Int
|
|
||||||
// Curve configures the desired packet.Curve if the Algorithm is PubKeyAlgoECDSA,
|
|
||||||
// PubKeyAlgoEdDSA, or PubKeyAlgoECDH. If empty Curve25519 is used.
|
|
||||||
Curve Curve
|
|
||||||
// AEADConfig configures the use of the new AEAD Encrypted Data Packet,
|
|
||||||
// defined in the draft of the next version of the OpenPGP specification.
|
|
||||||
// If a non-nil AEADConfig is passed, usage of this packet is enabled. By
|
|
||||||
// default, it is disabled. See the documentation of AEADConfig for more
|
|
||||||
// configuration options related to AEAD.
|
|
||||||
// **Note: using this option may break compatibility with other OpenPGP
|
|
||||||
// implementations, as well as future versions of this library.**
|
|
||||||
AEADConfig *AEADConfig
|
|
||||||
// V5Keys configures version 5 key generation. If false, this package still
|
|
||||||
// supports version 5 keys, but produces version 4 keys.
|
|
||||||
V5Keys bool
|
|
||||||
// "The validity period of the key. This is the number of seconds after
|
|
||||||
// the key creation time that the key expires. If this is not present
|
|
||||||
// or has a value of zero, the key never expires. This is found only on
|
|
||||||
// a self-signature.""
|
|
||||||
// https://tools.ietf.org/html/rfc4880#section-5.2.3.6
|
|
||||||
KeyLifetimeSecs uint32
|
|
||||||
// "The validity period of the signature. This is the number of seconds
|
|
||||||
// after the signature creation time that the signature expires. If
|
|
||||||
// this is not present or has a value of zero, it never expires."
|
|
||||||
// https://tools.ietf.org/html/rfc4880#section-5.2.3.10
|
|
||||||
SigLifetimeSecs uint32
|
|
||||||
// SigningKeyId is used to specify the signing key to use (by Key ID).
|
|
||||||
// By default, the signing key is selected automatically, preferring
|
|
||||||
// signing subkeys if available.
|
|
||||||
SigningKeyId uint64
|
|
||||||
// SigningIdentity is used to specify a user ID (packet Signer's User ID, type 28)
|
|
||||||
// when producing a generic certification signature onto an existing user ID.
|
|
||||||
// The identity must be present in the signer Entity.
|
|
||||||
SigningIdentity string
|
|
||||||
// InsecureAllowUnauthenticatedMessages controls, whether it is tolerated to read
|
|
||||||
// encrypted messages without Modification Detection Code (MDC).
|
|
||||||
// MDC is mandated by the IETF OpenPGP Crypto Refresh draft and has long been implemented
|
|
||||||
// in most OpenPGP implementations. Messages without MDC are considered unnecessarily
|
|
||||||
// insecure and should be prevented whenever possible.
|
|
||||||
// In case one needs to deal with messages from very old OpenPGP implementations, there
|
|
||||||
// might be no other way than to tolerate the missing MDC. Setting this flag, allows this
|
|
||||||
// mode of operation. It should be considered a measure of last resort.
|
|
||||||
InsecureAllowUnauthenticatedMessages bool
|
|
||||||
// KnownNotations is a map of Notation Data names to bools, which controls
|
|
||||||
// the notation names that are allowed to be present in critical Notation Data
|
|
||||||
// signature subpackets.
|
|
||||||
KnownNotations map[string]bool
|
|
||||||
// SignatureNotations is a list of Notations to be added to any signatures.
|
|
||||||
SignatureNotations []*Notation
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Random() io.Reader {
|
|
||||||
if c == nil || c.Rand == nil {
|
|
||||||
return rand.Reader
|
|
||||||
}
|
|
||||||
return c.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Hash() crypto.Hash {
|
|
||||||
if c == nil || uint(c.DefaultHash) == 0 {
|
|
||||||
return crypto.SHA256
|
|
||||||
}
|
|
||||||
return c.DefaultHash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Cipher() CipherFunction {
|
|
||||||
if c == nil || uint8(c.DefaultCipher) == 0 {
|
|
||||||
return CipherAES128
|
|
||||||
}
|
|
||||||
return c.DefaultCipher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Now() time.Time {
|
|
||||||
if c == nil || c.Time == nil {
|
|
||||||
return time.Now().Truncate(time.Second)
|
|
||||||
}
|
|
||||||
return c.Time().Truncate(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyLifetime returns the validity period of the key.
|
|
||||||
func (c *Config) KeyLifetime() uint32 {
|
|
||||||
if c == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return c.KeyLifetimeSecs
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigLifetime returns the validity period of the signature.
|
|
||||||
func (c *Config) SigLifetime() uint32 {
|
|
||||||
if c == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return c.SigLifetimeSecs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Compression() CompressionAlgo {
|
|
||||||
if c == nil {
|
|
||||||
return CompressionNone
|
|
||||||
}
|
|
||||||
return c.DefaultCompressionAlgo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) RSAModulusBits() int {
|
|
||||||
if c == nil || c.RSABits == 0 {
|
|
||||||
return 2048
|
|
||||||
}
|
|
||||||
return c.RSABits
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) PublicKeyAlgorithm() PublicKeyAlgorithm {
|
|
||||||
if c == nil || c.Algorithm == 0 {
|
|
||||||
return PubKeyAlgoRSA
|
|
||||||
}
|
|
||||||
return c.Algorithm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) CurveName() Curve {
|
|
||||||
if c == nil || c.Curve == "" {
|
|
||||||
return Curve25519
|
|
||||||
}
|
|
||||||
return c.Curve
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: The hash iterations should now be queried via the S2K() method.
|
|
||||||
func (c *Config) PasswordHashIterations() int {
|
|
||||||
if c == nil || c.S2KCount == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return c.S2KCount
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) S2K() *s2k.Config {
|
|
||||||
if c == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// for backwards compatibility
|
|
||||||
if c != nil && c.S2KCount > 0 && c.S2KConfig == nil {
|
|
||||||
return &s2k.Config{
|
|
||||||
S2KCount: c.S2KCount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.S2KConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) AEAD() *AEADConfig {
|
|
||||||
if c == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.AEADConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) SigningKey() uint64 {
|
|
||||||
if c == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return c.SigningKeyId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) SigningUserId() string {
|
|
||||||
if c == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.SigningIdentity
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) AllowUnauthenticatedMessages() bool {
|
|
||||||
if c == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return c.InsecureAllowUnauthenticatedMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) KnownNotation(notationName string) bool {
|
|
||||||
if c == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return c.KnownNotations[notationName]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Notations() []*Notation {
|
|
||||||
if c == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.SignatureNotations
|
|
||||||
}
|
|
@ -1,286 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
|
|
||||||
)
|
|
||||||
|
|
||||||
const encryptedKeyVersion = 3
|
|
||||||
|
|
||||||
// EncryptedKey represents a public-key encrypted session key. See RFC 4880,
|
|
||||||
// section 5.1.
|
|
||||||
type EncryptedKey struct {
|
|
||||||
KeyId uint64
|
|
||||||
Algo PublicKeyAlgorithm
|
|
||||||
CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet
|
|
||||||
Key []byte // only valid after a successful Decrypt
|
|
||||||
|
|
||||||
encryptedMPI1, encryptedMPI2 encoding.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EncryptedKey) parse(r io.Reader) (err error) {
|
|
||||||
var buf [10]byte
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0] != encryptedKeyVersion {
|
|
||||||
return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
e.KeyId = binary.BigEndian.Uint64(buf[1:9])
|
|
||||||
e.Algo = PublicKeyAlgorithm(buf[9])
|
|
||||||
switch e.Algo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
e.encryptedMPI1 = new(encoding.MPI)
|
|
||||||
if _, err = e.encryptedMPI1.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
e.encryptedMPI1 = new(encoding.MPI)
|
|
||||||
if _, err = e.encryptedMPI1.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.encryptedMPI2 = new(encoding.MPI)
|
|
||||||
if _, err = e.encryptedMPI2.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
e.encryptedMPI1 = new(encoding.MPI)
|
|
||||||
if _, err = e.encryptedMPI1.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.encryptedMPI2 = new(encoding.OID)
|
|
||||||
if _, err = e.encryptedMPI2.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = consumeAll(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func checksumKeyMaterial(key []byte) uint16 {
|
|
||||||
var checksum uint16
|
|
||||||
for _, v := range key {
|
|
||||||
checksum += uint16(v)
|
|
||||||
}
|
|
||||||
return checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt decrypts an encrypted session key with the given private key. The
|
|
||||||
// private key must have been decrypted first.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
|
|
||||||
if e.KeyId != 0 && e.KeyId != priv.KeyId {
|
|
||||||
return errors.InvalidArgumentError("cannot decrypt encrypted session key for key id " + strconv.FormatUint(e.KeyId, 16) + " with private key id " + strconv.FormatUint(priv.KeyId, 16))
|
|
||||||
}
|
|
||||||
if e.Algo != priv.PubKeyAlgo {
|
|
||||||
return errors.InvalidArgumentError("cannot decrypt encrypted session key of type " + strconv.Itoa(int(e.Algo)) + " with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
if priv.Dummy() {
|
|
||||||
return errors.ErrDummyPrivateKey("dummy key found")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var b []byte
|
|
||||||
|
|
||||||
// TODO(agl): use session key decryption routines here to avoid
|
|
||||||
// padding oracle attacks.
|
|
||||||
switch priv.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
// Supports both *rsa.PrivateKey and crypto.Decrypter
|
|
||||||
k := priv.PrivateKey.(crypto.Decrypter)
|
|
||||||
b, err = k.Decrypt(config.Random(), padToKeySize(k.Public().(*rsa.PublicKey), e.encryptedMPI1.Bytes()), nil)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
c1 := new(big.Int).SetBytes(e.encryptedMPI1.Bytes())
|
|
||||||
c2 := new(big.Int).SetBytes(e.encryptedMPI2.Bytes())
|
|
||||||
b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2)
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
vsG := e.encryptedMPI1.Bytes()
|
|
||||||
m := e.encryptedMPI2.Bytes()
|
|
||||||
oid := priv.PublicKey.oid.EncodedBytes()
|
|
||||||
b, err = ecdh.Decrypt(priv.PrivateKey.(*ecdh.PrivateKey), vsG, m, oid, priv.PublicKey.Fingerprint[:])
|
|
||||||
default:
|
|
||||||
err = errors.InvalidArgumentError("cannot decrypt encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.CipherFunc = CipherFunction(b[0])
|
|
||||||
if !e.CipherFunc.IsSupported() {
|
|
||||||
return errors.UnsupportedError("unsupported encryption function")
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Key = b[1 : len(b)-2]
|
|
||||||
expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
|
|
||||||
checksum := checksumKeyMaterial(e.Key)
|
|
||||||
if checksum != expectedChecksum {
|
|
||||||
return errors.StructuralError("EncryptedKey checksum incorrect")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize writes the encrypted key packet, e, to w.
|
|
||||||
func (e *EncryptedKey) Serialize(w io.Writer) error {
|
|
||||||
var mpiLen int
|
|
||||||
switch e.Algo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
mpiLen = int(e.encryptedMPI1.EncodedLength())
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
mpiLen = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength())
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
mpiLen = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength())
|
|
||||||
default:
|
|
||||||
return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := serializeHeader(w, packetTypeEncryptedKey, 1 /* version */ +8 /* key id */ +1 /* algo */ +mpiLen)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte{encryptedKeyVersion})
|
|
||||||
binary.Write(w, binary.BigEndian, e.KeyId)
|
|
||||||
w.Write([]byte{byte(e.Algo)})
|
|
||||||
|
|
||||||
switch e.Algo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
_, err := w.Write(e.encryptedMPI1.EncodedBytes())
|
|
||||||
return err
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
if _, err := w.Write(e.encryptedMPI1.EncodedBytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := w.Write(e.encryptedMPI2.EncodedBytes())
|
|
||||||
return err
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
if _, err := w.Write(e.encryptedMPI1.EncodedBytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := w.Write(e.encryptedMPI2.EncodedBytes())
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
panic("internal error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeEncryptedKey serializes an encrypted key packet to w that contains
|
|
||||||
// key, encrypted to pub.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error {
|
|
||||||
var buf [10]byte
|
|
||||||
buf[0] = encryptedKeyVersion
|
|
||||||
binary.BigEndian.PutUint64(buf[1:9], pub.KeyId)
|
|
||||||
buf[9] = byte(pub.PubKeyAlgo)
|
|
||||||
|
|
||||||
keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */)
|
|
||||||
keyBlock[0] = byte(cipherFunc)
|
|
||||||
copy(keyBlock[1:], key)
|
|
||||||
checksum := checksumKeyMaterial(key)
|
|
||||||
keyBlock[1+len(key)] = byte(checksum >> 8)
|
|
||||||
keyBlock[1+len(key)+1] = byte(checksum)
|
|
||||||
|
|
||||||
switch pub.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
return serializeEncryptedKeyRSA(w, config.Random(), buf, pub.PublicKey.(*rsa.PublicKey), keyBlock)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
return serializeEncryptedKeyElGamal(w, config.Random(), buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock)
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
return serializeEncryptedKeyECDH(w, config.Random(), buf, pub.PublicKey.(*ecdh.PublicKey), keyBlock, pub.oid, pub.Fingerprint)
|
|
||||||
case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly:
|
|
||||||
return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error {
|
|
||||||
cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)
|
|
||||||
if err != nil {
|
|
||||||
return errors.InvalidArgumentError("RSA encryption failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
cipherMPI := encoding.NewMPI(cipherText)
|
|
||||||
packetLen := 10 /* header length */ + int(cipherMPI.EncodedLength())
|
|
||||||
|
|
||||||
err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(header[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(cipherMPI.EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error {
|
|
||||||
c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock)
|
|
||||||
if err != nil {
|
|
||||||
return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
packetLen := 10 /* header length */
|
|
||||||
packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8
|
|
||||||
packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8
|
|
||||||
|
|
||||||
err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(header[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err = w.Write(new(encoding.MPI).SetBig(c1).EncodedBytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(new(encoding.MPI).SetBig(c2).EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header [10]byte, pub *ecdh.PublicKey, keyBlock []byte, oid encoding.Field, fingerprint []byte) error {
|
|
||||||
vsG, c, err := ecdh.Encrypt(rand, pub, keyBlock, oid.EncodedBytes(), fingerprint)
|
|
||||||
if err != nil {
|
|
||||||
return errors.InvalidArgumentError("ECDH encryption failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
g := encoding.NewMPI(vsG)
|
|
||||||
m := encoding.NewOID(c)
|
|
||||||
|
|
||||||
packetLen := 10 /* header length */
|
|
||||||
packetLen += int(g.EncodedLength()) + int(m.EncodedLength())
|
|
||||||
|
|
||||||
err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = w.Write(header[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err = w.Write(g.EncodedBytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(m.EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LiteralData represents an encrypted file. See RFC 4880, section 5.9.
|
|
||||||
type LiteralData struct {
|
|
||||||
Format uint8
|
|
||||||
IsBinary bool
|
|
||||||
FileName string
|
|
||||||
Time uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined.
|
|
||||||
Body io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForEyesOnly returns whether the contents of the LiteralData have been marked
|
|
||||||
// as especially sensitive.
|
|
||||||
func (l *LiteralData) ForEyesOnly() bool {
|
|
||||||
return l.FileName == "_CONSOLE"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LiteralData) parse(r io.Reader) (err error) {
|
|
||||||
var buf [256]byte
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Format = buf[0]
|
|
||||||
l.IsBinary = l.Format == 'b'
|
|
||||||
fileNameLen := int(buf[1])
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:fileNameLen])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.FileName = string(buf[:fileNameLen])
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:4])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Time = binary.BigEndian.Uint32(buf[:4])
|
|
||||||
l.Body = r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeLiteral serializes a literal data packet to w and returns a
|
|
||||||
// WriteCloser to which the data itself can be written and which MUST be closed
|
|
||||||
// on completion. The fileName is truncated to 255 bytes.
|
|
||||||
func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) {
|
|
||||||
var buf [4]byte
|
|
||||||
buf[0] = 't'
|
|
||||||
if isBinary {
|
|
||||||
buf[0] = 'b'
|
|
||||||
}
|
|
||||||
if len(fileName) > 255 {
|
|
||||||
fileName = fileName[:255]
|
|
||||||
}
|
|
||||||
buf[1] = byte(len(fileName))
|
|
||||||
|
|
||||||
inner, err := serializeStreamHeader(w, packetTypeLiteralData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = inner.Write(buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = inner.Write([]byte(fileName))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint32(buf[:], time)
|
|
||||||
_, err = inner.Write(buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
plaintext = inner
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package packet
|
|
||||||
|
|
||||||
// Notation type represents a Notation Data subpacket
|
|
||||||
// see https://tools.ietf.org/html/rfc4880#section-5.2.3.16
|
|
||||||
type Notation struct {
|
|
||||||
Name string
|
|
||||||
Value []byte
|
|
||||||
IsCritical bool
|
|
||||||
IsHumanReadable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (notation *Notation) getData() []byte {
|
|
||||||
nameData := []byte(notation.Name)
|
|
||||||
nameLen := len(nameData)
|
|
||||||
valueLen := len(notation.Value)
|
|
||||||
|
|
||||||
data := make([]byte, 8+nameLen+valueLen)
|
|
||||||
if notation.IsHumanReadable {
|
|
||||||
data[0] = 0x80
|
|
||||||
}
|
|
||||||
|
|
||||||
data[4] = byte(nameLen >> 8)
|
|
||||||
data[5] = byte(nameLen)
|
|
||||||
data[6] = byte(valueLen >> 8)
|
|
||||||
data[7] = byte(valueLen)
|
|
||||||
copy(data[8:8+nameLen], nameData)
|
|
||||||
copy(data[8+nameLen:], notation.Value)
|
|
||||||
return data
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// OpenPGP CFB Mode. http://tools.ietf.org/html/rfc4880#section-13.9
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ocfbEncrypter struct {
|
|
||||||
b cipher.Block
|
|
||||||
fre []byte
|
|
||||||
outUsed int
|
|
||||||
}
|
|
||||||
|
|
||||||
// An OCFBResyncOption determines if the "resynchronization step" of OCFB is
|
|
||||||
// performed.
|
|
||||||
type OCFBResyncOption bool
|
|
||||||
|
|
||||||
const (
|
|
||||||
OCFBResync OCFBResyncOption = true
|
|
||||||
OCFBNoResync OCFBResyncOption = false
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewOCFBEncrypter returns a cipher.Stream which encrypts data with OpenPGP's
|
|
||||||
// cipher feedback mode using the given cipher.Block, and an initial amount of
|
|
||||||
// ciphertext. randData must be random bytes and be the same length as the
|
|
||||||
// cipher.Block's block size. Resync determines if the "resynchronization step"
|
|
||||||
// from RFC 4880, 13.9 step 7 is performed. Different parts of OpenPGP vary on
|
|
||||||
// this point.
|
|
||||||
func NewOCFBEncrypter(block cipher.Block, randData []byte, resync OCFBResyncOption) (cipher.Stream, []byte) {
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
if len(randData) != blockSize {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
x := &ocfbEncrypter{
|
|
||||||
b: block,
|
|
||||||
fre: make([]byte, blockSize),
|
|
||||||
outUsed: 0,
|
|
||||||
}
|
|
||||||
prefix := make([]byte, blockSize+2)
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, x.fre)
|
|
||||||
for i := 0; i < blockSize; i++ {
|
|
||||||
prefix[i] = randData[i] ^ x.fre[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, prefix[:blockSize])
|
|
||||||
prefix[blockSize] = x.fre[0] ^ randData[blockSize-2]
|
|
||||||
prefix[blockSize+1] = x.fre[1] ^ randData[blockSize-1]
|
|
||||||
|
|
||||||
if resync {
|
|
||||||
block.Encrypt(x.fre, prefix[2:])
|
|
||||||
} else {
|
|
||||||
x.fre[0] = prefix[blockSize]
|
|
||||||
x.fre[1] = prefix[blockSize+1]
|
|
||||||
x.outUsed = 2
|
|
||||||
}
|
|
||||||
return x, prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ocfbEncrypter) XORKeyStream(dst, src []byte) {
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if x.outUsed == len(x.fre) {
|
|
||||||
x.b.Encrypt(x.fre, x.fre)
|
|
||||||
x.outUsed = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
x.fre[x.outUsed] ^= src[i]
|
|
||||||
dst[i] = x.fre[x.outUsed]
|
|
||||||
x.outUsed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ocfbDecrypter struct {
|
|
||||||
b cipher.Block
|
|
||||||
fre []byte
|
|
||||||
outUsed int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOCFBDecrypter returns a cipher.Stream which decrypts data with OpenPGP's
|
|
||||||
// cipher feedback mode using the given cipher.Block. Prefix must be the first
|
|
||||||
// blockSize + 2 bytes of the ciphertext, where blockSize is the cipher.Block's
|
|
||||||
// block size. On successful exit, blockSize+2 bytes of decrypted data are written into
|
|
||||||
// prefix. Resync determines if the "resynchronization step" from RFC 4880,
|
|
||||||
// 13.9 step 7 is performed. Different parts of OpenPGP vary on this point.
|
|
||||||
func NewOCFBDecrypter(block cipher.Block, prefix []byte, resync OCFBResyncOption) cipher.Stream {
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
if len(prefix) != blockSize+2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
x := &ocfbDecrypter{
|
|
||||||
b: block,
|
|
||||||
fre: make([]byte, blockSize),
|
|
||||||
outUsed: 0,
|
|
||||||
}
|
|
||||||
prefixCopy := make([]byte, len(prefix))
|
|
||||||
copy(prefixCopy, prefix)
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, x.fre)
|
|
||||||
for i := 0; i < blockSize; i++ {
|
|
||||||
prefixCopy[i] ^= x.fre[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
block.Encrypt(x.fre, prefix[:blockSize])
|
|
||||||
prefixCopy[blockSize] ^= x.fre[0]
|
|
||||||
prefixCopy[blockSize+1] ^= x.fre[1]
|
|
||||||
|
|
||||||
if resync {
|
|
||||||
block.Encrypt(x.fre, prefix[2:])
|
|
||||||
} else {
|
|
||||||
x.fre[0] = prefix[blockSize]
|
|
||||||
x.fre[1] = prefix[blockSize+1]
|
|
||||||
x.outUsed = 2
|
|
||||||
}
|
|
||||||
copy(prefix, prefixCopy)
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ocfbDecrypter) XORKeyStream(dst, src []byte) {
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if x.outUsed == len(x.fre) {
|
|
||||||
x.b.Encrypt(x.fre, x.fre)
|
|
||||||
x.outUsed = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
c := src[i]
|
|
||||||
dst[i] = x.fre[x.outUsed] ^ src[i]
|
|
||||||
x.fre[x.outUsed] = c
|
|
||||||
x.outUsed++
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"encoding/binary"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OnePassSignature represents a one-pass signature packet. See RFC 4880,
|
|
||||||
// section 5.4.
|
|
||||||
type OnePassSignature struct {
|
|
||||||
SigType SignatureType
|
|
||||||
Hash crypto.Hash
|
|
||||||
PubKeyAlgo PublicKeyAlgorithm
|
|
||||||
KeyId uint64
|
|
||||||
IsLast bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const onePassSignatureVersion = 3
|
|
||||||
|
|
||||||
func (ops *OnePassSignature) parse(r io.Reader) (err error) {
|
|
||||||
var buf [13]byte
|
|
||||||
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0] != onePassSignatureVersion {
|
|
||||||
err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
ops.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2])
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
|
|
||||||
}
|
|
||||||
|
|
||||||
ops.SigType = SignatureType(buf[1])
|
|
||||||
ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3])
|
|
||||||
ops.KeyId = binary.BigEndian.Uint64(buf[4:12])
|
|
||||||
ops.IsLast = buf[12] != 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize marshals the given OnePassSignature to w.
|
|
||||||
func (ops *OnePassSignature) Serialize(w io.Writer) error {
|
|
||||||
var buf [13]byte
|
|
||||||
buf[0] = onePassSignatureVersion
|
|
||||||
buf[1] = uint8(ops.SigType)
|
|
||||||
var ok bool
|
|
||||||
buf[2], ok = algorithm.HashToHashIdWithSha1(ops.Hash)
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
|
|
||||||
}
|
|
||||||
buf[3] = uint8(ops.PubKeyAlgo)
|
|
||||||
binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
|
|
||||||
if ops.IsLast {
|
|
||||||
buf[12] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := w.Write(buf[:])
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
// 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 packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpaquePacket represents an OpenPGP packet as raw, unparsed data. This is
|
|
||||||
// useful for splitting and storing the original packet contents separately,
|
|
||||||
// handling unsupported packet types or accessing parts of the packet not yet
|
|
||||||
// implemented by this package.
|
|
||||||
type OpaquePacket struct {
|
|
||||||
// Packet type
|
|
||||||
Tag uint8
|
|
||||||
// Reason why the packet was parsed opaquely
|
|
||||||
Reason error
|
|
||||||
// Binary contents of the packet data
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *OpaquePacket) parse(r io.Reader) (err error) {
|
|
||||||
op.Contents, err = ioutil.ReadAll(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize marshals the packet to a writer in its original form, including
|
|
||||||
// the packet header.
|
|
||||||
func (op *OpaquePacket) Serialize(w io.Writer) (err error) {
|
|
||||||
err = serializeHeader(w, packetType(op.Tag), len(op.Contents))
|
|
||||||
if err == nil {
|
|
||||||
_, err = w.Write(op.Contents)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse attempts to parse the opaque contents into a structure supported by
|
|
||||||
// this package. If the packet is not known then the result will be another
|
|
||||||
// OpaquePacket.
|
|
||||||
func (op *OpaquePacket) Parse() (p Packet, err error) {
|
|
||||||
hdr := bytes.NewBuffer(nil)
|
|
||||||
err = serializeHeader(hdr, packetType(op.Tag), len(op.Contents))
|
|
||||||
if err != nil {
|
|
||||||
op.Reason = err
|
|
||||||
return op, err
|
|
||||||
}
|
|
||||||
p, err = Read(io.MultiReader(hdr, bytes.NewBuffer(op.Contents)))
|
|
||||||
if err != nil {
|
|
||||||
op.Reason = err
|
|
||||||
p = op
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpaqueReader reads OpaquePackets from an io.Reader.
|
|
||||||
type OpaqueReader struct {
|
|
||||||
r io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpaqueReader(r io.Reader) *OpaqueReader {
|
|
||||||
return &OpaqueReader{r: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the next OpaquePacket.
|
|
||||||
func (or *OpaqueReader) Next() (op *OpaquePacket, err error) {
|
|
||||||
tag, _, contents, err := readHeader(or.r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
op = &OpaquePacket{Tag: uint8(tag), Reason: err}
|
|
||||||
err = op.parse(contents)
|
|
||||||
if err != nil {
|
|
||||||
consumeAll(contents)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpaqueSubpacket represents an unparsed OpenPGP subpacket,
|
|
||||||
// as found in signature and user attribute packets.
|
|
||||||
type OpaqueSubpacket struct {
|
|
||||||
SubType uint8
|
|
||||||
EncodedLength []byte // Store the original encoded length for signature verifications.
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from
|
|
||||||
// their byte representation.
|
|
||||||
func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) {
|
|
||||||
var (
|
|
||||||
subHeaderLen int
|
|
||||||
subPacket *OpaqueSubpacket
|
|
||||||
)
|
|
||||||
for len(contents) > 0 {
|
|
||||||
subHeaderLen, subPacket, err = nextSubpacket(contents)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
result = append(result, subPacket)
|
|
||||||
contents = contents[subHeaderLen+len(subPacket.Contents):]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) {
|
|
||||||
// RFC 4880, section 5.2.3.1
|
|
||||||
var subLen uint32
|
|
||||||
var encodedLength []byte
|
|
||||||
if len(contents) < 1 {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
subPacket = &OpaqueSubpacket{}
|
|
||||||
switch {
|
|
||||||
case contents[0] < 192:
|
|
||||||
subHeaderLen = 2 // 1 length byte, 1 subtype byte
|
|
||||||
if len(contents) < subHeaderLen {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
encodedLength = contents[0:1]
|
|
||||||
subLen = uint32(contents[0])
|
|
||||||
contents = contents[1:]
|
|
||||||
case contents[0] < 255:
|
|
||||||
subHeaderLen = 3 // 2 length bytes, 1 subtype
|
|
||||||
if len(contents) < subHeaderLen {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
encodedLength = contents[0:2]
|
|
||||||
subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192
|
|
||||||
contents = contents[2:]
|
|
||||||
default:
|
|
||||||
subHeaderLen = 6 // 5 length bytes, 1 subtype
|
|
||||||
if len(contents) < subHeaderLen {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
encodedLength = contents[0:5]
|
|
||||||
subLen = uint32(contents[1])<<24 |
|
|
||||||
uint32(contents[2])<<16 |
|
|
||||||
uint32(contents[3])<<8 |
|
|
||||||
uint32(contents[4])
|
|
||||||
contents = contents[5:]
|
|
||||||
|
|
||||||
}
|
|
||||||
if subLen > uint32(len(contents)) || subLen == 0 {
|
|
||||||
goto Truncated
|
|
||||||
}
|
|
||||||
subPacket.SubType = contents[0]
|
|
||||||
subPacket.EncodedLength = encodedLength
|
|
||||||
subPacket.Contents = contents[1:subLen]
|
|
||||||
return
|
|
||||||
Truncated:
|
|
||||||
err = errors.StructuralError("subpacket truncated")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) {
|
|
||||||
buf := make([]byte, 6)
|
|
||||||
copy(buf, osp.EncodedLength)
|
|
||||||
n := len(osp.EncodedLength)
|
|
||||||
|
|
||||||
buf[n] = osp.SubType
|
|
||||||
if _, err = w.Write(buf[:n+1]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(osp.Contents)
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,551 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package packet implements parsing and serialization of OpenPGP packets, as
|
|
||||||
// specified in RFC 4880.
|
|
||||||
package packet // import "github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rsa"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// readFull is the same as io.ReadFull except that reading zero bytes returns
|
|
||||||
// ErrUnexpectedEOF rather than EOF.
|
|
||||||
func readFull(r io.Reader, buf []byte) (n int, err error) {
|
|
||||||
n, err = io.ReadFull(r, buf)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2.
|
|
||||||
func readLength(r io.Reader) (length int64, isPartial bool, err error) {
|
|
||||||
var buf [4]byte
|
|
||||||
_, err = readFull(r, buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case buf[0] < 192:
|
|
||||||
length = int64(buf[0])
|
|
||||||
case buf[0] < 224:
|
|
||||||
length = int64(buf[0]-192) << 8
|
|
||||||
_, err = readFull(r, buf[0:1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
length += int64(buf[0]) + 192
|
|
||||||
case buf[0] < 255:
|
|
||||||
length = int64(1) << (buf[0] & 0x1f)
|
|
||||||
isPartial = true
|
|
||||||
default:
|
|
||||||
_, err = readFull(r, buf[0:4])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
length = int64(buf[0])<<24 |
|
|
||||||
int64(buf[1])<<16 |
|
|
||||||
int64(buf[2])<<8 |
|
|
||||||
int64(buf[3])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths.
|
|
||||||
// The continuation lengths are parsed and removed from the stream and EOF is
|
|
||||||
// returned at the end of the packet. See RFC 4880, section 4.2.2.4.
|
|
||||||
type partialLengthReader struct {
|
|
||||||
r io.Reader
|
|
||||||
remaining int64
|
|
||||||
isPartial bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *partialLengthReader) Read(p []byte) (n int, err error) {
|
|
||||||
for r.remaining == 0 {
|
|
||||||
if !r.isPartial {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
r.remaining, r.isPartial, err = readLength(r.r)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toRead := int64(len(p))
|
|
||||||
if toRead > r.remaining {
|
|
||||||
toRead = r.remaining
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = r.r.Read(p[:int(toRead)])
|
|
||||||
r.remaining -= int64(n)
|
|
||||||
if n < int(toRead) && err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialLengthWriter writes a stream of data using OpenPGP partial lengths.
|
|
||||||
// See RFC 4880, section 4.2.2.4.
|
|
||||||
type partialLengthWriter struct {
|
|
||||||
w io.WriteCloser
|
|
||||||
buf bytes.Buffer
|
|
||||||
lengthByte [1]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *partialLengthWriter) Write(p []byte) (n int, err error) {
|
|
||||||
bufLen := w.buf.Len()
|
|
||||||
if bufLen > 512 {
|
|
||||||
for power := uint(30); ; power-- {
|
|
||||||
l := 1 << power
|
|
||||||
if bufLen >= l {
|
|
||||||
w.lengthByte[0] = 224 + uint8(power)
|
|
||||||
_, err = w.w.Write(w.lengthByte[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var m int
|
|
||||||
m, err = w.w.Write(w.buf.Next(l))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m != l {
|
|
||||||
return 0, io.ErrShortWrite
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.buf.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *partialLengthWriter) Close() (err error) {
|
|
||||||
len := w.buf.Len()
|
|
||||||
err = serializeLength(w.w, len)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.buf.WriteTo(w.w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.w.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the
|
|
||||||
// underlying Reader returns EOF before the limit has been reached.
|
|
||||||
type spanReader struct {
|
|
||||||
r io.Reader
|
|
||||||
n int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *spanReader) Read(p []byte) (n int, err error) {
|
|
||||||
if l.n <= 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
if int64(len(p)) > l.n {
|
|
||||||
p = p[0:l.n]
|
|
||||||
}
|
|
||||||
n, err = l.r.Read(p)
|
|
||||||
l.n -= int64(n)
|
|
||||||
if l.n > 0 && err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// readHeader parses a packet header and returns an io.Reader which will return
|
|
||||||
// the contents of the packet. See RFC 4880, section 4.2.
|
|
||||||
func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) {
|
|
||||||
var buf [4]byte
|
|
||||||
_, err = io.ReadFull(r, buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0]&0x80 == 0 {
|
|
||||||
err = errors.StructuralError("tag byte does not have MSB set")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0]&0x40 == 0 {
|
|
||||||
// Old format packet
|
|
||||||
tag = packetType((buf[0] & 0x3f) >> 2)
|
|
||||||
lengthType := buf[0] & 3
|
|
||||||
if lengthType == 3 {
|
|
||||||
length = -1
|
|
||||||
contents = r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lengthBytes := 1 << lengthType
|
|
||||||
_, err = readFull(r, buf[0:lengthBytes])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; i < lengthBytes; i++ {
|
|
||||||
length <<= 8
|
|
||||||
length |= int64(buf[i])
|
|
||||||
}
|
|
||||||
contents = &spanReader{r, length}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// New format packet
|
|
||||||
tag = packetType(buf[0] & 0x3f)
|
|
||||||
length, isPartial, err := readLength(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isPartial {
|
|
||||||
contents = &partialLengthReader{
|
|
||||||
remaining: length,
|
|
||||||
isPartial: true,
|
|
||||||
r: r,
|
|
||||||
}
|
|
||||||
length = -1
|
|
||||||
} else {
|
|
||||||
contents = &spanReader{r, length}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section
|
|
||||||
// 4.2.
|
|
||||||
func serializeHeader(w io.Writer, ptype packetType, length int) (err error) {
|
|
||||||
err = serializeType(w, ptype)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return serializeLength(w, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeType writes an OpenPGP packet type to w. See RFC 4880, section
|
|
||||||
// 4.2.
|
|
||||||
func serializeType(w io.Writer, ptype packetType) (err error) {
|
|
||||||
var buf [1]byte
|
|
||||||
buf[0] = 0x80 | 0x40 | byte(ptype)
|
|
||||||
_, err = w.Write(buf[:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeLength writes an OpenPGP packet length to w. See RFC 4880, section
|
|
||||||
// 4.2.2.
|
|
||||||
func serializeLength(w io.Writer, length int) (err error) {
|
|
||||||
var buf [5]byte
|
|
||||||
var n int
|
|
||||||
|
|
||||||
if length < 192 {
|
|
||||||
buf[0] = byte(length)
|
|
||||||
n = 1
|
|
||||||
} else if length < 8384 {
|
|
||||||
length -= 192
|
|
||||||
buf[0] = 192 + byte(length>>8)
|
|
||||||
buf[1] = byte(length)
|
|
||||||
n = 2
|
|
||||||
} else {
|
|
||||||
buf[0] = 255
|
|
||||||
buf[1] = byte(length >> 24)
|
|
||||||
buf[2] = byte(length >> 16)
|
|
||||||
buf[3] = byte(length >> 8)
|
|
||||||
buf[4] = byte(length)
|
|
||||||
n = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = w.Write(buf[:n])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeStreamHeader writes an OpenPGP packet header to w where the
|
|
||||||
// length of the packet is unknown. It returns a io.WriteCloser which can be
|
|
||||||
// used to write the contents of the packet. See RFC 4880, section 4.2.
|
|
||||||
func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) {
|
|
||||||
err = serializeType(w, ptype)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out = &partialLengthWriter{w: w}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packet represents an OpenPGP packet. Users are expected to try casting
|
|
||||||
// instances of this interface to specific packet types.
|
|
||||||
type Packet interface {
|
|
||||||
parse(io.Reader) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// consumeAll reads from the given Reader until error, returning the number of
|
|
||||||
// bytes read.
|
|
||||||
func consumeAll(r io.Reader) (n int64, err error) {
|
|
||||||
var m int
|
|
||||||
var buf [1024]byte
|
|
||||||
|
|
||||||
for {
|
|
||||||
m, err = r.Read(buf[:])
|
|
||||||
n += int64(m)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// packetType represents the numeric ids of the different OpenPGP packet types. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2
|
|
||||||
type packetType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
packetTypeEncryptedKey packetType = 1
|
|
||||||
packetTypeSignature packetType = 2
|
|
||||||
packetTypeSymmetricKeyEncrypted packetType = 3
|
|
||||||
packetTypeOnePassSignature packetType = 4
|
|
||||||
packetTypePrivateKey packetType = 5
|
|
||||||
packetTypePublicKey packetType = 6
|
|
||||||
packetTypePrivateSubkey packetType = 7
|
|
||||||
packetTypeCompressed packetType = 8
|
|
||||||
packetTypeSymmetricallyEncrypted packetType = 9
|
|
||||||
packetTypeLiteralData packetType = 11
|
|
||||||
packetTypeUserId packetType = 13
|
|
||||||
packetTypePublicSubkey packetType = 14
|
|
||||||
packetTypeUserAttribute packetType = 17
|
|
||||||
packetTypeSymmetricallyEncryptedIntegrityProtected packetType = 18
|
|
||||||
packetTypeAEADEncrypted packetType = 20
|
|
||||||
)
|
|
||||||
|
|
||||||
// EncryptedDataPacket holds encrypted data. It is currently implemented by
|
|
||||||
// SymmetricallyEncrypted and AEADEncrypted.
|
|
||||||
type EncryptedDataPacket interface {
|
|
||||||
Decrypt(CipherFunction, []byte) (io.ReadCloser, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads a single OpenPGP packet from the given io.Reader. If there is an
|
|
||||||
// error parsing a packet, the whole packet is consumed from the input.
|
|
||||||
func Read(r io.Reader) (p Packet, err error) {
|
|
||||||
tag, _, contents, err := readHeader(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case packetTypeEncryptedKey:
|
|
||||||
p = new(EncryptedKey)
|
|
||||||
case packetTypeSignature:
|
|
||||||
p = new(Signature)
|
|
||||||
case packetTypeSymmetricKeyEncrypted:
|
|
||||||
p = new(SymmetricKeyEncrypted)
|
|
||||||
case packetTypeOnePassSignature:
|
|
||||||
p = new(OnePassSignature)
|
|
||||||
case packetTypePrivateKey, packetTypePrivateSubkey:
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
if tag == packetTypePrivateSubkey {
|
|
||||||
pk.IsSubkey = true
|
|
||||||
}
|
|
||||||
p = pk
|
|
||||||
case packetTypePublicKey, packetTypePublicSubkey:
|
|
||||||
isSubkey := tag == packetTypePublicSubkey
|
|
||||||
p = &PublicKey{IsSubkey: isSubkey}
|
|
||||||
case packetTypeCompressed:
|
|
||||||
p = new(Compressed)
|
|
||||||
case packetTypeSymmetricallyEncrypted:
|
|
||||||
p = new(SymmetricallyEncrypted)
|
|
||||||
case packetTypeLiteralData:
|
|
||||||
p = new(LiteralData)
|
|
||||||
case packetTypeUserId:
|
|
||||||
p = new(UserId)
|
|
||||||
case packetTypeUserAttribute:
|
|
||||||
p = new(UserAttribute)
|
|
||||||
case packetTypeSymmetricallyEncryptedIntegrityProtected:
|
|
||||||
se := new(SymmetricallyEncrypted)
|
|
||||||
se.IntegrityProtected = true
|
|
||||||
p = se
|
|
||||||
case packetTypeAEADEncrypted:
|
|
||||||
p = new(AEADEncrypted)
|
|
||||||
default:
|
|
||||||
err = errors.UnknownPacketTypeError(tag)
|
|
||||||
}
|
|
||||||
if p != nil {
|
|
||||||
err = p.parse(contents)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
consumeAll(contents)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignatureType represents the different semantic meanings of an OpenPGP
|
|
||||||
// signature. See RFC 4880, section 5.2.1.
|
|
||||||
type SignatureType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
SigTypeBinary SignatureType = 0x00
|
|
||||||
SigTypeText = 0x01
|
|
||||||
SigTypeGenericCert = 0x10
|
|
||||||
SigTypePersonaCert = 0x11
|
|
||||||
SigTypeCasualCert = 0x12
|
|
||||||
SigTypePositiveCert = 0x13
|
|
||||||
SigTypeSubkeyBinding = 0x18
|
|
||||||
SigTypePrimaryKeyBinding = 0x19
|
|
||||||
SigTypeDirectSignature = 0x1F
|
|
||||||
SigTypeKeyRevocation = 0x20
|
|
||||||
SigTypeSubkeyRevocation = 0x28
|
|
||||||
SigTypeCertificationRevocation = 0x30
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKeyAlgorithm represents the different public key system specified for
|
|
||||||
// OpenPGP. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12
|
|
||||||
type PublicKeyAlgorithm uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
PubKeyAlgoRSA PublicKeyAlgorithm = 1
|
|
||||||
PubKeyAlgoElGamal PublicKeyAlgorithm = 16
|
|
||||||
PubKeyAlgoDSA PublicKeyAlgorithm = 17
|
|
||||||
// RFC 6637, Section 5.
|
|
||||||
PubKeyAlgoECDH PublicKeyAlgorithm = 18
|
|
||||||
PubKeyAlgoECDSA PublicKeyAlgorithm = 19
|
|
||||||
// https://www.ietf.org/archive/id/draft-koch-eddsa-for-openpgp-04.txt
|
|
||||||
PubKeyAlgoEdDSA PublicKeyAlgorithm = 22
|
|
||||||
|
|
||||||
// Deprecated in RFC 4880, Section 13.5. Use key flags instead.
|
|
||||||
PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2
|
|
||||||
PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// CanEncrypt returns true if it's possible to encrypt a message to a public
|
|
||||||
// key of the given type.
|
|
||||||
func (pka PublicKeyAlgorithm) CanEncrypt() bool {
|
|
||||||
switch pka {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanSign returns true if it's possible for a public key of the given type to
|
|
||||||
// sign a message.
|
|
||||||
func (pka PublicKeyAlgorithm) CanSign() bool {
|
|
||||||
switch pka {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA, PubKeyAlgoEdDSA:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CipherFunction represents the different block ciphers specified for OpenPGP. See
|
|
||||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13
|
|
||||||
type CipherFunction algorithm.CipherFunction
|
|
||||||
|
|
||||||
const (
|
|
||||||
Cipher3DES CipherFunction = 2
|
|
||||||
CipherCAST5 CipherFunction = 3
|
|
||||||
CipherAES128 CipherFunction = 7
|
|
||||||
CipherAES192 CipherFunction = 8
|
|
||||||
CipherAES256 CipherFunction = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeySize returns the key size, in bytes, of cipher.
|
|
||||||
func (cipher CipherFunction) KeySize() int {
|
|
||||||
return algorithm.CipherFunction(cipher).KeySize()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSupported returns true if the cipher is supported from the library
|
|
||||||
func (cipher CipherFunction) IsSupported() bool {
|
|
||||||
return algorithm.CipherFunction(cipher).KeySize() > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockSize returns the block size, in bytes, of cipher.
|
|
||||||
func (cipher CipherFunction) blockSize() int {
|
|
||||||
return algorithm.CipherFunction(cipher).BlockSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
// new returns a fresh instance of the given cipher.
|
|
||||||
func (cipher CipherFunction) new(key []byte) (block cipher.Block) {
|
|
||||||
return algorithm.CipherFunction(cipher).New(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// padToKeySize left-pads a MPI with zeroes to match the length of the
|
|
||||||
// specified RSA public.
|
|
||||||
func padToKeySize(pub *rsa.PublicKey, b []byte) []byte {
|
|
||||||
k := (pub.N.BitLen() + 7) / 8
|
|
||||||
if len(b) >= k {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
bb := make([]byte, k)
|
|
||||||
copy(bb[len(bb)-len(b):], b)
|
|
||||||
return bb
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompressionAlgo Represents the different compression algorithms
|
|
||||||
// supported by OpenPGP (except for BZIP2, which is not currently
|
|
||||||
// supported). See Section 9.3 of RFC 4880.
|
|
||||||
type CompressionAlgo uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
CompressionNone CompressionAlgo = 0
|
|
||||||
CompressionZIP CompressionAlgo = 1
|
|
||||||
CompressionZLIB CompressionAlgo = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// AEADMode represents the different Authenticated Encryption with Associated
|
|
||||||
// Data specified for OpenPGP.
|
|
||||||
// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6
|
|
||||||
type AEADMode algorithm.AEADMode
|
|
||||||
|
|
||||||
const (
|
|
||||||
AEADModeEAX AEADMode = 1
|
|
||||||
AEADModeOCB AEADMode = 2
|
|
||||||
AEADModeGCM AEADMode = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
func (mode AEADMode) IvLength() int {
|
|
||||||
return algorithm.AEADMode(mode).NonceLength()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mode AEADMode) TagLength() int {
|
|
||||||
return algorithm.AEADMode(mode).TagLength()
|
|
||||||
}
|
|
||||||
|
|
||||||
// new returns a fresh instance of the given mode.
|
|
||||||
func (mode AEADMode) new(block cipher.Block) cipher.AEAD {
|
|
||||||
return algorithm.AEADMode(mode).New(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReasonForRevocation represents a revocation reason code as per RFC4880
|
|
||||||
// section 5.2.3.23.
|
|
||||||
type ReasonForRevocation uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
NoReason ReasonForRevocation = 0
|
|
||||||
KeySuperseded ReasonForRevocation = 1
|
|
||||||
KeyCompromised ReasonForRevocation = 2
|
|
||||||
KeyRetired ReasonForRevocation = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// Curve is a mapping to supported ECC curves for key generation.
|
|
||||||
// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#name-curve-specific-wire-formats
|
|
||||||
type Curve string
|
|
||||||
|
|
||||||
const (
|
|
||||||
Curve25519 Curve = "Curve25519"
|
|
||||||
Curve448 Curve = "Curve448"
|
|
||||||
CurveNistP256 Curve = "P256"
|
|
||||||
CurveNistP384 Curve = "P384"
|
|
||||||
CurveNistP521 Curve = "P521"
|
|
||||||
CurveSecP256k1 Curve = "SecP256k1"
|
|
||||||
CurveBrainpoolP256 Curve = "BrainpoolP256"
|
|
||||||
CurveBrainpoolP384 Curve = "BrainpoolP384"
|
|
||||||
CurveBrainpoolP512 Curve = "BrainpoolP512"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TrustLevel represents a trust level per RFC4880 5.2.3.13
|
|
||||||
type TrustLevel uint8
|
|
||||||
|
|
||||||
// TrustAmount represents a trust amount per RFC4880 5.2.3.13
|
|
||||||
type TrustAmount uint8
|
|
@ -1,837 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrivateKey represents a possibly encrypted private key. See RFC 4880,
|
|
||||||
// section 5.5.3.
|
|
||||||
type PrivateKey struct {
|
|
||||||
PublicKey
|
|
||||||
Encrypted bool // if true then the private key is unavailable until Decrypt has been called.
|
|
||||||
encryptedData []byte
|
|
||||||
cipher CipherFunction
|
|
||||||
s2k func(out, in []byte)
|
|
||||||
// An *{rsa|dsa|elgamal|ecdh|ecdsa|ed25519}.PrivateKey or
|
|
||||||
// crypto.Signer/crypto.Decrypter (Decryptor RSA only).
|
|
||||||
PrivateKey interface{}
|
|
||||||
sha1Checksum bool
|
|
||||||
iv []byte
|
|
||||||
|
|
||||||
// Type of encryption of the S2K packet
|
|
||||||
// Allowed values are 0 (Not encrypted), 254 (SHA1), or
|
|
||||||
// 255 (2-byte checksum)
|
|
||||||
s2kType S2KType
|
|
||||||
// Full parameters of the S2K packet
|
|
||||||
s2kParams *s2k.Params
|
|
||||||
}
|
|
||||||
|
|
||||||
// S2KType s2k packet type
|
|
||||||
type S2KType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// S2KNON unencrypt
|
|
||||||
S2KNON S2KType = 0
|
|
||||||
// S2KSHA1 sha1 sum check
|
|
||||||
S2KSHA1 S2KType = 254
|
|
||||||
// S2KCHECKSUM sum check
|
|
||||||
S2KCHECKSUM S2KType = 255
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRSAPrivateKey(creationTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewRSAPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDSAPrivateKey(creationTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewDSAPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewElGamalPrivateKey(creationTime time.Time, priv *elgamal.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewElGamalPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECDSAPrivateKey(creationTime time.Time, priv *ecdsa.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewECDSAPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEdDSAPrivateKey(creationTime time.Time, priv *eddsa.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECDHPrivateKey(creationTime time.Time, priv *ecdh.PrivateKey) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
pk.PublicKey = *NewECDHPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignerPrivateKey creates a PrivateKey from a crypto.Signer that
|
|
||||||
// implements RSA, ECDSA or EdDSA.
|
|
||||||
func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
// In general, the public Keys should be used as pointers. We still
|
|
||||||
// type-switch on the values, for backwards-compatibility.
|
|
||||||
switch pubkey := signer.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey.PublicKey)
|
|
||||||
case rsa.PrivateKey:
|
|
||||||
pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey.PublicKey)
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey.PublicKey)
|
|
||||||
case ecdsa.PrivateKey:
|
|
||||||
pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey.PublicKey)
|
|
||||||
case *eddsa.PrivateKey:
|
|
||||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey)
|
|
||||||
case eddsa.PrivateKey:
|
|
||||||
pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey)
|
|
||||||
default:
|
|
||||||
panic("openpgp: unknown signer type in NewSignerPrivateKey")
|
|
||||||
}
|
|
||||||
pk.PrivateKey = signer
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecrypterPrivateKey creates a PrivateKey from a *{rsa|elgamal|ecdh}.PrivateKey.
|
|
||||||
func NewDecrypterPrivateKey(creationTime time.Time, decrypter interface{}) *PrivateKey {
|
|
||||||
pk := new(PrivateKey)
|
|
||||||
switch priv := decrypter.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
pk.PublicKey = *NewRSAPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
case *elgamal.PrivateKey:
|
|
||||||
pk.PublicKey = *NewElGamalPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
case *ecdh.PrivateKey:
|
|
||||||
pk.PublicKey = *NewECDHPublicKey(creationTime, &priv.PublicKey)
|
|
||||||
default:
|
|
||||||
panic("openpgp: unknown decrypter type in NewDecrypterPrivateKey")
|
|
||||||
}
|
|
||||||
pk.PrivateKey = decrypter
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parse(r io.Reader) (err error) {
|
|
||||||
err = (&pk.PublicKey).parse(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v5 := pk.PublicKey.Version == 5
|
|
||||||
|
|
||||||
var buf [1]byte
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.s2kType = S2KType(buf[0])
|
|
||||||
var optCount [1]byte
|
|
||||||
if v5 {
|
|
||||||
if _, err = readFull(r, optCount[:]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pk.s2kType {
|
|
||||||
case S2KNON:
|
|
||||||
pk.s2k = nil
|
|
||||||
pk.Encrypted = false
|
|
||||||
case S2KSHA1, S2KCHECKSUM:
|
|
||||||
if v5 && pk.s2kType == S2KCHECKSUM {
|
|
||||||
return errors.StructuralError("wrong s2k identifier for version 5")
|
|
||||||
}
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.cipher = CipherFunction(buf[0])
|
|
||||||
if pk.cipher != 0 && !pk.cipher.IsSupported() {
|
|
||||||
return errors.UnsupportedError("unsupported cipher function in private key")
|
|
||||||
}
|
|
||||||
pk.s2kParams, err = s2k.ParseIntoParams(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pk.s2kParams.Dummy() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.s2k, err = pk.s2kParams.Function()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.Encrypted = true
|
|
||||||
if pk.s2kType == S2KSHA1 {
|
|
||||||
pk.sha1Checksum = true
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.UnsupportedError("deprecated s2k function in private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk.Encrypted {
|
|
||||||
blockSize := pk.cipher.blockSize()
|
|
||||||
if blockSize == 0 {
|
|
||||||
return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
|
|
||||||
}
|
|
||||||
pk.iv = make([]byte, blockSize)
|
|
||||||
_, err = readFull(r, pk.iv)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var privateKeyData []byte
|
|
||||||
if v5 {
|
|
||||||
var n [4]byte /* secret material four octet count */
|
|
||||||
_, err = readFull(r, n[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
count := uint32(uint32(n[0])<<24 | uint32(n[1])<<16 | uint32(n[2])<<8 | uint32(n[3]))
|
|
||||||
if !pk.Encrypted {
|
|
||||||
count = count + 2 /* two octet checksum */
|
|
||||||
}
|
|
||||||
privateKeyData = make([]byte, count)
|
|
||||||
_, err = readFull(r, privateKeyData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
privateKeyData, err = ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !pk.Encrypted {
|
|
||||||
if len(privateKeyData) < 2 {
|
|
||||||
return errors.StructuralError("truncated private key data")
|
|
||||||
}
|
|
||||||
var sum uint16
|
|
||||||
for i := 0; i < len(privateKeyData)-2; i++ {
|
|
||||||
sum += uint16(privateKeyData[i])
|
|
||||||
}
|
|
||||||
if privateKeyData[len(privateKeyData)-2] != uint8(sum>>8) ||
|
|
||||||
privateKeyData[len(privateKeyData)-1] != uint8(sum) {
|
|
||||||
return errors.StructuralError("private key checksum failure")
|
|
||||||
}
|
|
||||||
privateKeyData = privateKeyData[:len(privateKeyData)-2]
|
|
||||||
return pk.parsePrivateKey(privateKeyData)
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.encryptedData = privateKeyData
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dummy returns true if the private key is a dummy key. This is a GNU extension.
|
|
||||||
func (pk *PrivateKey) Dummy() bool {
|
|
||||||
return pk.s2kParams.Dummy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func mod64kHash(d []byte) uint16 {
|
|
||||||
var h uint16
|
|
||||||
for _, b := range d {
|
|
||||||
h += uint16(b)
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
|
|
||||||
contents := bytes.NewBuffer(nil)
|
|
||||||
err = pk.PublicKey.serializeWithoutHeaders(contents)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = contents.Write([]byte{uint8(pk.s2kType)}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
optional := bytes.NewBuffer(nil)
|
|
||||||
if pk.Encrypted || pk.Dummy() {
|
|
||||||
optional.Write([]byte{uint8(pk.cipher)})
|
|
||||||
if err := pk.s2kParams.Serialize(optional); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pk.Encrypted {
|
|
||||||
optional.Write(pk.iv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pk.Version == 5 {
|
|
||||||
contents.Write([]byte{uint8(optional.Len())})
|
|
||||||
}
|
|
||||||
io.Copy(contents, optional)
|
|
||||||
|
|
||||||
if !pk.Dummy() {
|
|
||||||
l := 0
|
|
||||||
var priv []byte
|
|
||||||
if !pk.Encrypted {
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
err = pk.serializePrivateKey(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l = buf.Len()
|
|
||||||
checksum := mod64kHash(buf.Bytes())
|
|
||||||
buf.Write([]byte{byte(checksum >> 8), byte(checksum)})
|
|
||||||
priv = buf.Bytes()
|
|
||||||
} else {
|
|
||||||
priv, l = pk.encryptedData, len(pk.encryptedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk.Version == 5 {
|
|
||||||
contents.Write([]byte{byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l)})
|
|
||||||
}
|
|
||||||
contents.Write(priv)
|
|
||||||
}
|
|
||||||
|
|
||||||
ptype := packetTypePrivateKey
|
|
||||||
if pk.IsSubkey {
|
|
||||||
ptype = packetTypePrivateSubkey
|
|
||||||
}
|
|
||||||
err = serializeHeader(w, ptype, contents.Len())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = io.Copy(w, contents)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error {
|
|
||||||
if _, err := w.Write(new(encoding.MPI).SetBig(priv.D).EncodedBytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(new(encoding.MPI).SetBig(priv.Primes[1]).EncodedBytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(new(encoding.MPI).SetBig(priv.Primes[0]).EncodedBytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := w.Write(new(encoding.MPI).SetBig(priv.Precomputed.Qinv).EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error {
|
|
||||||
_, err := w.Write(new(encoding.MPI).SetBig(priv.X).EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeElGamalPrivateKey(w io.Writer, priv *elgamal.PrivateKey) error {
|
|
||||||
_, err := w.Write(new(encoding.MPI).SetBig(priv.X).EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeECDSAPrivateKey(w io.Writer, priv *ecdsa.PrivateKey) error {
|
|
||||||
_, err := w.Write(encoding.NewMPI(priv.MarshalIntegerSecret()).EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeEdDSAPrivateKey(w io.Writer, priv *eddsa.PrivateKey) error {
|
|
||||||
_, err := w.Write(encoding.NewMPI(priv.MarshalByteSecret()).EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeECDHPrivateKey(w io.Writer, priv *ecdh.PrivateKey) error {
|
|
||||||
_, err := w.Write(encoding.NewMPI(priv.MarshalByteSecret()).EncodedBytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt decrypts an encrypted private key using a decryption key.
|
|
||||||
func (pk *PrivateKey) decrypt(decryptionKey []byte) error {
|
|
||||||
if pk.Dummy() {
|
|
||||||
return errors.ErrDummyPrivateKey("dummy key found")
|
|
||||||
}
|
|
||||||
if !pk.Encrypted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := pk.cipher.new(decryptionKey)
|
|
||||||
cfb := cipher.NewCFBDecrypter(block, pk.iv)
|
|
||||||
|
|
||||||
data := make([]byte, len(pk.encryptedData))
|
|
||||||
cfb.XORKeyStream(data, pk.encryptedData)
|
|
||||||
|
|
||||||
if pk.sha1Checksum {
|
|
||||||
if len(data) < sha1.Size {
|
|
||||||
return errors.StructuralError("truncated private key data")
|
|
||||||
}
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write(data[:len(data)-sha1.Size])
|
|
||||||
sum := h.Sum(nil)
|
|
||||||
if !bytes.Equal(sum, data[len(data)-sha1.Size:]) {
|
|
||||||
return errors.StructuralError("private key checksum failure")
|
|
||||||
}
|
|
||||||
data = data[:len(data)-sha1.Size]
|
|
||||||
} else {
|
|
||||||
if len(data) < 2 {
|
|
||||||
return errors.StructuralError("truncated private key data")
|
|
||||||
}
|
|
||||||
var sum uint16
|
|
||||||
for i := 0; i < len(data)-2; i++ {
|
|
||||||
sum += uint16(data[i])
|
|
||||||
}
|
|
||||||
if data[len(data)-2] != uint8(sum>>8) ||
|
|
||||||
data[len(data)-1] != uint8(sum) {
|
|
||||||
return errors.StructuralError("private key checksum failure")
|
|
||||||
}
|
|
||||||
data = data[:len(data)-2]
|
|
||||||
}
|
|
||||||
|
|
||||||
err := pk.parsePrivateKey(data)
|
|
||||||
if _, ok := err.(errors.KeyInvalidError); ok {
|
|
||||||
return errors.KeyInvalidError("invalid key parameters")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark key as unencrypted
|
|
||||||
pk.s2kType = S2KNON
|
|
||||||
pk.s2k = nil
|
|
||||||
pk.Encrypted = false
|
|
||||||
pk.encryptedData = nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) decryptWithCache(passphrase []byte, keyCache *s2k.Cache) error {
|
|
||||||
if pk.Dummy() {
|
|
||||||
return errors.ErrDummyPrivateKey("dummy key found")
|
|
||||||
}
|
|
||||||
if !pk.Encrypted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := keyCache.GetOrComputeDerivedKey(passphrase, pk.s2kParams, pk.cipher.KeySize())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pk.decrypt(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt decrypts an encrypted private key using a passphrase.
|
|
||||||
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
|
||||||
if pk.Dummy() {
|
|
||||||
return errors.ErrDummyPrivateKey("dummy key found")
|
|
||||||
}
|
|
||||||
if !pk.Encrypted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
key := make([]byte, pk.cipher.KeySize())
|
|
||||||
pk.s2k(key, passphrase)
|
|
||||||
return pk.decrypt(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptPrivateKeys decrypts all encrypted keys with the given config and passphrase.
|
|
||||||
// Avoids recomputation of similar s2k key derivations.
|
|
||||||
func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error {
|
|
||||||
// Create a cache to avoid recomputation of key derviations for the same passphrase.
|
|
||||||
s2kCache := &s2k.Cache{}
|
|
||||||
for _, key := range keys {
|
|
||||||
if key != nil && !key.Dummy() && key.Encrypted {
|
|
||||||
err := key.decryptWithCache(passphrase, s2kCache)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypt encrypts an unencrypted private key.
|
|
||||||
func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, cipherFunction CipherFunction) error {
|
|
||||||
if pk.Dummy() {
|
|
||||||
return errors.ErrDummyPrivateKey("dummy key found")
|
|
||||||
}
|
|
||||||
if pk.Encrypted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// check if encryptionKey has the correct size
|
|
||||||
if len(key) != cipherFunction.KeySize() {
|
|
||||||
return errors.InvalidArgumentError("supplied encryption key has the wrong size")
|
|
||||||
}
|
|
||||||
|
|
||||||
priv := bytes.NewBuffer(nil)
|
|
||||||
err := pk.serializePrivateKey(priv)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.cipher = cipherFunction
|
|
||||||
pk.s2kParams = params
|
|
||||||
pk.s2k, err = pk.s2kParams.Function()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKeyBytes := priv.Bytes()
|
|
||||||
pk.sha1Checksum = true
|
|
||||||
block := pk.cipher.new(key)
|
|
||||||
pk.iv = make([]byte, pk.cipher.blockSize())
|
|
||||||
_, err = rand.Read(pk.iv)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cfb := cipher.NewCFBEncrypter(block, pk.iv)
|
|
||||||
|
|
||||||
if pk.sha1Checksum {
|
|
||||||
pk.s2kType = S2KSHA1
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write(privateKeyBytes)
|
|
||||||
sum := h.Sum(nil)
|
|
||||||
privateKeyBytes = append(privateKeyBytes, sum...)
|
|
||||||
} else {
|
|
||||||
pk.s2kType = S2KCHECKSUM
|
|
||||||
var sum uint16
|
|
||||||
for _, b := range privateKeyBytes {
|
|
||||||
sum += uint16(b)
|
|
||||||
}
|
|
||||||
priv.Write([]byte{uint8(sum >> 8), uint8(sum)})
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.encryptedData = make([]byte, len(privateKeyBytes))
|
|
||||||
cfb.XORKeyStream(pk.encryptedData, privateKeyBytes)
|
|
||||||
pk.Encrypted = true
|
|
||||||
pk.PrivateKey = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptWithConfig encrypts an unencrypted private key using the passphrase and the config.
|
|
||||||
func (pk *PrivateKey) EncryptWithConfig(passphrase []byte, config *Config) error {
|
|
||||||
params, err := s2k.Generate(config.Random(), config.S2K())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Derive an encryption key with the configured s2k function.
|
|
||||||
key := make([]byte, config.Cipher().KeySize())
|
|
||||||
s2k, err := params.Function()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s2k(key, passphrase)
|
|
||||||
// Encrypt the private key with the derived encryption key.
|
|
||||||
return pk.encrypt(key, params, config.Cipher())
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptPrivateKeys encrypts all unencrypted keys with the given config and passphrase.
|
|
||||||
// Only derives one key from the passphrase, which is then used to encrypt each key.
|
|
||||||
func EncryptPrivateKeys(keys []*PrivateKey, passphrase []byte, config *Config) error {
|
|
||||||
params, err := s2k.Generate(config.Random(), config.S2K())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Derive an encryption key with the configured s2k function.
|
|
||||||
encryptionKey := make([]byte, config.Cipher().KeySize())
|
|
||||||
s2k, err := params.Function()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s2k(encryptionKey, passphrase)
|
|
||||||
for _, key := range keys {
|
|
||||||
if key != nil && !key.Dummy() && !key.Encrypted {
|
|
||||||
err = key.encrypt(encryptionKey, params, config.Cipher())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt encrypts an unencrypted private key using a passphrase.
|
|
||||||
func (pk *PrivateKey) Encrypt(passphrase []byte) error {
|
|
||||||
// Default config of private key encryption
|
|
||||||
config := &Config{
|
|
||||||
S2KConfig: &s2k.Config{
|
|
||||||
S2KMode: s2k.IteratedSaltedS2K,
|
|
||||||
S2KCount: 65536,
|
|
||||||
Hash: crypto.SHA256,
|
|
||||||
} ,
|
|
||||||
DefaultCipher: CipherAES256,
|
|
||||||
}
|
|
||||||
return pk.EncryptWithConfig(passphrase, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) {
|
|
||||||
switch priv := pk.PrivateKey.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
err = serializeRSAPrivateKey(w, priv)
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
err = serializeDSAPrivateKey(w, priv)
|
|
||||||
case *elgamal.PrivateKey:
|
|
||||||
err = serializeElGamalPrivateKey(w, priv)
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
err = serializeECDSAPrivateKey(w, priv)
|
|
||||||
case *eddsa.PrivateKey:
|
|
||||||
err = serializeEdDSAPrivateKey(w, priv)
|
|
||||||
case *ecdh.PrivateKey:
|
|
||||||
err = serializeECDHPrivateKey(w, priv)
|
|
||||||
default:
|
|
||||||
err = errors.InvalidArgumentError("unknown private key type")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
|
|
||||||
switch pk.PublicKey.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly:
|
|
||||||
return pk.parseRSAPrivateKey(data)
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
return pk.parseDSAPrivateKey(data)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
return pk.parseElGamalPrivateKey(data)
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
return pk.parseECDSAPrivateKey(data)
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
return pk.parseECDHPrivateKey(data)
|
|
||||||
case PubKeyAlgoEdDSA:
|
|
||||||
return pk.parseEdDSAPrivateKey(data)
|
|
||||||
}
|
|
||||||
panic("impossible")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) {
|
|
||||||
rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey)
|
|
||||||
rsaPriv := new(rsa.PrivateKey)
|
|
||||||
rsaPriv.PublicKey = *rsaPub
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
d := new(encoding.MPI)
|
|
||||||
if _, err := d.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := new(encoding.MPI)
|
|
||||||
if _, err := p.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
q := new(encoding.MPI)
|
|
||||||
if _, err := q.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsaPriv.D = new(big.Int).SetBytes(d.Bytes())
|
|
||||||
rsaPriv.Primes = make([]*big.Int, 2)
|
|
||||||
rsaPriv.Primes[0] = new(big.Int).SetBytes(p.Bytes())
|
|
||||||
rsaPriv.Primes[1] = new(big.Int).SetBytes(q.Bytes())
|
|
||||||
if err := rsaPriv.Validate(); err != nil {
|
|
||||||
return errors.KeyInvalidError(err.Error())
|
|
||||||
}
|
|
||||||
rsaPriv.Precompute()
|
|
||||||
pk.PrivateKey = rsaPriv
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) {
|
|
||||||
dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey)
|
|
||||||
dsaPriv := new(dsa.PrivateKey)
|
|
||||||
dsaPriv.PublicKey = *dsaPub
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
x := new(encoding.MPI)
|
|
||||||
if _, err := x.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dsaPriv.X = new(big.Int).SetBytes(x.Bytes())
|
|
||||||
if err := validateDSAParameters(dsaPriv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pk.PrivateKey = dsaPriv
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
|
|
||||||
pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey)
|
|
||||||
priv := new(elgamal.PrivateKey)
|
|
||||||
priv.PublicKey = *pub
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
x := new(encoding.MPI)
|
|
||||||
if _, err := x.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
priv.X = new(big.Int).SetBytes(x.Bytes())
|
|
||||||
if err := validateElGamalParameters(priv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pk.PrivateKey = priv
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
|
|
||||||
ecdsaPub := pk.PublicKey.PublicKey.(*ecdsa.PublicKey)
|
|
||||||
ecdsaPriv := ecdsa.NewPrivateKey(*ecdsaPub)
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
d := new(encoding.MPI)
|
|
||||||
if _, err := d.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ecdsaPriv.UnmarshalIntegerSecret(d.Bytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ecdsa.Validate(ecdsaPriv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pk.PrivateKey = ecdsaPriv
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseECDHPrivateKey(data []byte) (err error) {
|
|
||||||
ecdhPub := pk.PublicKey.PublicKey.(*ecdh.PublicKey)
|
|
||||||
ecdhPriv := ecdh.NewPrivateKey(*ecdhPub)
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
d := new(encoding.MPI)
|
|
||||||
if _, err := d.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ecdhPriv.UnmarshalByteSecret(d.Bytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ecdh.Validate(ecdhPriv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.PrivateKey = ecdhPriv
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PrivateKey) parseEdDSAPrivateKey(data []byte) (err error) {
|
|
||||||
eddsaPub := pk.PublicKey.PublicKey.(*eddsa.PublicKey)
|
|
||||||
eddsaPriv := eddsa.NewPrivateKey(*eddsaPub)
|
|
||||||
eddsaPriv.PublicKey = *eddsaPub
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
d := new(encoding.MPI)
|
|
||||||
if _, err := d.ReadFrom(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = eddsaPriv.UnmarshalByteSecret(d.Bytes()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eddsa.Validate(eddsaPriv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.PrivateKey = eddsaPriv
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateDSAParameters(priv *dsa.PrivateKey) error {
|
|
||||||
p := priv.P // group prime
|
|
||||||
q := priv.Q // subgroup order
|
|
||||||
g := priv.G // g has order q mod p
|
|
||||||
x := priv.X // secret
|
|
||||||
y := priv.Y // y == g**x mod p
|
|
||||||
one := big.NewInt(1)
|
|
||||||
// expect g, y >= 2 and g < p
|
|
||||||
if g.Cmp(one) <= 0 || y.Cmp(one) <= 0 || g.Cmp(p) > 0 {
|
|
||||||
return errors.KeyInvalidError("dsa: invalid group")
|
|
||||||
}
|
|
||||||
// expect p > q
|
|
||||||
if p.Cmp(q) <= 0 {
|
|
||||||
return errors.KeyInvalidError("dsa: invalid group prime")
|
|
||||||
}
|
|
||||||
// q should be large enough and divide p-1
|
|
||||||
pSub1 := new(big.Int).Sub(p, one)
|
|
||||||
if q.BitLen() < 150 || new(big.Int).Mod(pSub1, q).Cmp(big.NewInt(0)) != 0 {
|
|
||||||
return errors.KeyInvalidError("dsa: invalid order")
|
|
||||||
}
|
|
||||||
// confirm that g has order q mod p
|
|
||||||
if !q.ProbablyPrime(32) || new(big.Int).Exp(g, q, p).Cmp(one) != 0 {
|
|
||||||
return errors.KeyInvalidError("dsa: invalid order")
|
|
||||||
}
|
|
||||||
// check y
|
|
||||||
if new(big.Int).Exp(g, x, p).Cmp(y) != 0 {
|
|
||||||
return errors.KeyInvalidError("dsa: mismatching values")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateElGamalParameters(priv *elgamal.PrivateKey) error {
|
|
||||||
p := priv.P // group prime
|
|
||||||
g := priv.G // g has order p-1 mod p
|
|
||||||
x := priv.X // secret
|
|
||||||
y := priv.Y // y == g**x mod p
|
|
||||||
one := big.NewInt(1)
|
|
||||||
// Expect g, y >= 2 and g < p
|
|
||||||
if g.Cmp(one) <= 0 || y.Cmp(one) <= 0 || g.Cmp(p) > 0 {
|
|
||||||
return errors.KeyInvalidError("elgamal: invalid group")
|
|
||||||
}
|
|
||||||
if p.BitLen() < 1024 {
|
|
||||||
return errors.KeyInvalidError("elgamal: group order too small")
|
|
||||||
}
|
|
||||||
pSub1 := new(big.Int).Sub(p, one)
|
|
||||||
if new(big.Int).Exp(g, pSub1, p).Cmp(one) != 0 {
|
|
||||||
return errors.KeyInvalidError("elgamal: invalid group")
|
|
||||||
}
|
|
||||||
// Since p-1 is not prime, g might have a smaller order that divides p-1.
|
|
||||||
// We cannot confirm the exact order of g, but we make sure it is not too small.
|
|
||||||
gExpI := new(big.Int).Set(g)
|
|
||||||
i := 1
|
|
||||||
threshold := 2 << 17 // we want order > threshold
|
|
||||||
for i < threshold {
|
|
||||||
i++ // we check every order to make sure key validation is not easily bypassed by guessing y'
|
|
||||||
gExpI.Mod(new(big.Int).Mul(gExpI, g), p)
|
|
||||||
if gExpI.Cmp(one) == 0 {
|
|
||||||
return errors.KeyInvalidError("elgamal: order too small")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check y
|
|
||||||
if new(big.Int).Exp(g, x, p).Cmp(y) != 0 {
|
|
||||||
return errors.KeyInvalidError("elgamal: mismatching values")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
12
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key_test_data.go
generated
vendored
12
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/private_key_test_data.go
generated
vendored
@ -1,12 +0,0 @@
|
|||||||
package packet
|
|
||||||
|
|
||||||
// Generated with `gpg --export-secret-keys "Test Key 2"`
|
|
||||||
const privKeyRSAHex = "9501fe044cc349a8010400b70ca0010e98c090008d45d1ee8f9113bd5861fd57b88bacb7c68658747663f1e1a3b5a98f32fda6472373c024b97359cd2efc88ff60f77751adfbf6af5e615e6a1408cfad8bf0cea30b0d5f53aa27ad59089ba9b15b7ebc2777a25d7b436144027e3bcd203909f147d0e332b240cf63d3395f5dfe0df0a6c04e8655af7eacdf0011010001fe0303024a252e7d475fd445607de39a265472aa74a9320ba2dac395faa687e9e0336aeb7e9a7397e511b5afd9dc84557c80ac0f3d4d7bfec5ae16f20d41c8c84a04552a33870b930420e230e179564f6d19bb153145e76c33ae993886c388832b0fa042ddda7f133924f3854481533e0ede31d51278c0519b29abc3bf53da673e13e3e1214b52413d179d7f66deee35cac8eacb060f78379d70ef4af8607e68131ff529439668fc39c9ce6dfef8a5ac234d234802cbfb749a26107db26406213ae5c06d4673253a3cbee1fcbae58d6ab77e38d6e2c0e7c6317c48e054edadb5a40d0d48acb44643d998139a8a66bb820be1f3f80185bc777d14b5954b60effe2448a036d565c6bc0b915fcea518acdd20ab07bc1529f561c58cd044f723109b93f6fd99f876ff891d64306b5d08f48bab59f38695e9109c4dec34013ba3153488ce070268381ba923ee1eb77125b36afcb4347ec3478c8f2735b06ef17351d872e577fa95d0c397c88c71b59629a36aec"
|
|
||||||
|
|
||||||
// Generated by `gpg --export-secret-keys` followed by a manual extraction of
|
|
||||||
// the ElGamal subkey from the packets.
|
|
||||||
const privKeyElGamalHex = "9d0157044df9ee1a100400eb8e136a58ec39b582629cdadf830bc64e0a94ed8103ca8bb247b27b11b46d1d25297ef4bcc3071785ba0c0bedfe89eabc5287fcc0edf81ab5896c1c8e4b20d27d79813c7aede75320b33eaeeaa586edc00fd1036c10133e6ba0ff277245d0d59d04b2b3421b7244aca5f4a8d870c6f1c1fbff9e1c26699a860b9504f35ca1d700030503fd1ededd3b840795be6d9ccbe3c51ee42e2f39233c432b831ddd9c4e72b7025a819317e47bf94f9ee316d7273b05d5fcf2999c3a681f519b1234bbfa6d359b4752bd9c3f77d6b6456cde152464763414ca130f4e91d91041432f90620fec0e6d6b5116076c2985d5aeaae13be492b9b329efcaf7ee25120159a0a30cd976b42d7afe030302dae7eb80db744d4960c4df930d57e87fe81412eaace9f900e6c839817a614ddb75ba6603b9417c33ea7b6c93967dfa2bcff3fa3c74a5ce2c962db65b03aece14c96cbd0038fc"
|
|
||||||
|
|
||||||
// pkcs1PrivKeyHex is a PKCS#1, RSA private key.
|
|
||||||
// Generated by `openssl genrsa 1024 | openssl rsa -outform DER | xxd -p`
|
|
||||||
const pkcs1PrivKeyHex = "3082025d02010002818100e98edfa1c3b35884a54d0b36a6a603b0290fa85e49e30fa23fc94fef9c6790bc4849928607aa48d809da326fb42a969d06ad756b98b9c1a90f5d4a2b6d0ac05953c97f4da3120164a21a679793ce181c906dc01d235cc085ddcdf6ea06c389b6ab8885dfd685959e693138856a68a7e5db263337ff82a088d583a897cf2d59e9020301000102818100b6d5c9eb70b02d5369b3ee5b520a14490b5bde8a317d36f7e4c74b7460141311d1e5067735f8f01d6f5908b2b96fbd881f7a1ab9a84d82753e39e19e2d36856be960d05ac9ef8e8782ea1b6d65aee28fdfe1d61451e8cff0adfe84322f12cf455028b581cf60eb9e0e140ba5d21aeba6c2634d7c65318b9a665fc01c3191ca21024100fa5e818da3705b0fa33278bb28d4b6f6050388af2d4b75ec9375dd91ccf2e7d7068086a8b82a8f6282e4fbbdb8a7f2622eb97295249d87acea7f5f816f54d347024100eecf9406d7dc49cdfb95ab1eff4064de84c7a30f64b2798936a0d2018ba9eb52e4b636f82e96c49cc63b80b675e91e40d1b2e4017d4b9adaf33ab3d9cf1c214f024100c173704ace742c082323066226a4655226819a85304c542b9dacbeacbf5d1881ee863485fcf6f59f3a604f9b42289282067447f2b13dfeed3eab7851fc81e0550240741fc41f3fc002b382eed8730e33c5d8de40256e4accee846667f536832f711ab1d4590e7db91a8a116ac5bff3be13d3f9243ff2e976662aa9b395d907f8e9c9024046a5696c9ef882363e06c9fa4e2f5b580906452befba03f4a99d0f873697ef1f851d2226ca7934b30b7c3e80cb634a67172bbbf4781735fe3e09263e2dd723e7"
|
|
@ -1,806 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
|
||||||
_ "crypto/sha512"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
|
|
||||||
)
|
|
||||||
|
|
||||||
type kdfHashFunction byte
|
|
||||||
type kdfAlgorithm byte
|
|
||||||
|
|
||||||
// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2.
|
|
||||||
type PublicKey struct {
|
|
||||||
Version int
|
|
||||||
CreationTime time.Time
|
|
||||||
PubKeyAlgo PublicKeyAlgorithm
|
|
||||||
PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or *eddsa.PublicKey
|
|
||||||
Fingerprint []byte
|
|
||||||
KeyId uint64
|
|
||||||
IsSubkey bool
|
|
||||||
|
|
||||||
// RFC 4880 fields
|
|
||||||
n, e, p, q, g, y encoding.Field
|
|
||||||
|
|
||||||
// RFC 6637 fields
|
|
||||||
// oid contains the OID byte sequence identifying the elliptic curve used
|
|
||||||
oid encoding.Field
|
|
||||||
|
|
||||||
// kdf stores key derivation function parameters
|
|
||||||
// used for ECDH encryption. See RFC 6637, Section 9.
|
|
||||||
kdf encoding.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpgradeToV5 updates the version of the key to v5, and updates all necessary
|
|
||||||
// fields.
|
|
||||||
func (pk *PublicKey) UpgradeToV5() {
|
|
||||||
pk.Version = 5
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
}
|
|
||||||
|
|
||||||
// signingKey provides a convenient abstraction over signature verification
|
|
||||||
// for v3 and v4 public keys.
|
|
||||||
type signingKey interface {
|
|
||||||
SerializeForHash(io.Writer) error
|
|
||||||
SerializeSignaturePrefix(io.Writer)
|
|
||||||
serializeWithoutHeaders(io.Writer) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
|
|
||||||
func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
Version: 4,
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoRSA,
|
|
||||||
PublicKey: pub,
|
|
||||||
n: new(encoding.MPI).SetBig(pub.N),
|
|
||||||
e: new(encoding.MPI).SetBig(big.NewInt(int64(pub.E))),
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDSAPublicKey returns a PublicKey that wraps the given dsa.PublicKey.
|
|
||||||
func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
Version: 4,
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoDSA,
|
|
||||||
PublicKey: pub,
|
|
||||||
p: new(encoding.MPI).SetBig(pub.P),
|
|
||||||
q: new(encoding.MPI).SetBig(pub.Q),
|
|
||||||
g: new(encoding.MPI).SetBig(pub.G),
|
|
||||||
y: new(encoding.MPI).SetBig(pub.Y),
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewElGamalPublicKey returns a PublicKey that wraps the given elgamal.PublicKey.
|
|
||||||
func NewElGamalPublicKey(creationTime time.Time, pub *elgamal.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
Version: 4,
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoElGamal,
|
|
||||||
PublicKey: pub,
|
|
||||||
p: new(encoding.MPI).SetBig(pub.P),
|
|
||||||
g: new(encoding.MPI).SetBig(pub.G),
|
|
||||||
y: new(encoding.MPI).SetBig(pub.Y),
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey {
|
|
||||||
pk := &PublicKey{
|
|
||||||
Version: 4,
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoECDSA,
|
|
||||||
PublicKey: pub,
|
|
||||||
p: encoding.NewMPI(pub.MarshalPoint()),
|
|
||||||
}
|
|
||||||
|
|
||||||
curveInfo := ecc.FindByCurve(pub.GetCurve())
|
|
||||||
if curveInfo == nil {
|
|
||||||
panic("unknown elliptic curve")
|
|
||||||
}
|
|
||||||
pk.oid = curveInfo.Oid
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECDHPublicKey(creationTime time.Time, pub *ecdh.PublicKey) *PublicKey {
|
|
||||||
var pk *PublicKey
|
|
||||||
var kdf = encoding.NewOID([]byte{0x1, pub.Hash.Id(), pub.Cipher.Id()})
|
|
||||||
pk = &PublicKey{
|
|
||||||
Version: 4,
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoECDH,
|
|
||||||
PublicKey: pub,
|
|
||||||
p: encoding.NewMPI(pub.MarshalPoint()),
|
|
||||||
kdf: kdf,
|
|
||||||
}
|
|
||||||
|
|
||||||
curveInfo := ecc.FindByCurve(pub.GetCurve())
|
|
||||||
|
|
||||||
if curveInfo == nil {
|
|
||||||
panic("unknown elliptic curve")
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.oid = curveInfo.Oid
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEdDSAPublicKey(creationTime time.Time, pub *eddsa.PublicKey) *PublicKey {
|
|
||||||
curveInfo := ecc.FindByCurve(pub.GetCurve())
|
|
||||||
pk := &PublicKey{
|
|
||||||
Version: 4,
|
|
||||||
CreationTime: creationTime,
|
|
||||||
PubKeyAlgo: PubKeyAlgoEdDSA,
|
|
||||||
PublicKey: pub,
|
|
||||||
oid: curveInfo.Oid,
|
|
||||||
// Native point format, see draft-koch-eddsa-for-openpgp-04, Appendix B
|
|
||||||
p: encoding.NewMPI(pub.MarshalPoint()),
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) parse(r io.Reader) (err error) {
|
|
||||||
// RFC 4880, section 5.5.2
|
|
||||||
var buf [6]byte
|
|
||||||
_, err = readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if buf[0] != 4 && buf[0] != 5 {
|
|
||||||
return errors.UnsupportedError("public key version " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.Version = int(buf[0])
|
|
||||||
if pk.Version == 5 {
|
|
||||||
var n [4]byte
|
|
||||||
_, err = readFull(r, n[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
|
|
||||||
pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5])
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
err = pk.parseRSA(r)
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
err = pk.parseDSA(r)
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
err = pk.parseElGamal(r)
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
err = pk.parseECDSA(r)
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
err = pk.parseECDH(r)
|
|
||||||
case PubKeyAlgoEdDSA:
|
|
||||||
err = pk.parseEdDSA(r)
|
|
||||||
default:
|
|
||||||
err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.setFingerprintAndKeyId()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) setFingerprintAndKeyId() {
|
|
||||||
// RFC 4880, section 12.2
|
|
||||||
if pk.Version == 5 {
|
|
||||||
fingerprint := sha256.New()
|
|
||||||
pk.SerializeForHash(fingerprint)
|
|
||||||
pk.Fingerprint = make([]byte, 32)
|
|
||||||
copy(pk.Fingerprint, fingerprint.Sum(nil))
|
|
||||||
pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[:8])
|
|
||||||
} else {
|
|
||||||
fingerprint := sha1.New()
|
|
||||||
pk.SerializeForHash(fingerprint)
|
|
||||||
pk.Fingerprint = make([]byte, 20)
|
|
||||||
copy(pk.Fingerprint, fingerprint.Sum(nil))
|
|
||||||
pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
|
|
||||||
// section 5.5.2.
|
|
||||||
func (pk *PublicKey) parseRSA(r io.Reader) (err error) {
|
|
||||||
pk.n = new(encoding.MPI)
|
|
||||||
if _, err = pk.n.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.e = new(encoding.MPI)
|
|
||||||
if _, err = pk.e.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pk.e.Bytes()) > 3 {
|
|
||||||
err = errors.UnsupportedError("large public exponent")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rsa := &rsa.PublicKey{
|
|
||||||
N: new(big.Int).SetBytes(pk.n.Bytes()),
|
|
||||||
E: 0,
|
|
||||||
}
|
|
||||||
for i := 0; i < len(pk.e.Bytes()); i++ {
|
|
||||||
rsa.E <<= 8
|
|
||||||
rsa.E |= int(pk.e.Bytes()[i])
|
|
||||||
}
|
|
||||||
pk.PublicKey = rsa
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDSA parses DSA public key material from the given Reader. See RFC 4880,
|
|
||||||
// section 5.5.2.
|
|
||||||
func (pk *PublicKey) parseDSA(r io.Reader) (err error) {
|
|
||||||
pk.p = new(encoding.MPI)
|
|
||||||
if _, err = pk.p.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.q = new(encoding.MPI)
|
|
||||||
if _, err = pk.q.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.g = new(encoding.MPI)
|
|
||||||
if _, err = pk.g.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.y = new(encoding.MPI)
|
|
||||||
if _, err = pk.y.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dsa := new(dsa.PublicKey)
|
|
||||||
dsa.P = new(big.Int).SetBytes(pk.p.Bytes())
|
|
||||||
dsa.Q = new(big.Int).SetBytes(pk.q.Bytes())
|
|
||||||
dsa.G = new(big.Int).SetBytes(pk.g.Bytes())
|
|
||||||
dsa.Y = new(big.Int).SetBytes(pk.y.Bytes())
|
|
||||||
pk.PublicKey = dsa
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseElGamal parses ElGamal public key material from the given Reader. See
|
|
||||||
// RFC 4880, section 5.5.2.
|
|
||||||
func (pk *PublicKey) parseElGamal(r io.Reader) (err error) {
|
|
||||||
pk.p = new(encoding.MPI)
|
|
||||||
if _, err = pk.p.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.g = new(encoding.MPI)
|
|
||||||
if _, err = pk.g.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.y = new(encoding.MPI)
|
|
||||||
if _, err = pk.y.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
elgamal := new(elgamal.PublicKey)
|
|
||||||
elgamal.P = new(big.Int).SetBytes(pk.p.Bytes())
|
|
||||||
elgamal.G = new(big.Int).SetBytes(pk.g.Bytes())
|
|
||||||
elgamal.Y = new(big.Int).SetBytes(pk.y.Bytes())
|
|
||||||
pk.PublicKey = elgamal
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseECDSA parses ECDSA public key material from the given Reader. See
|
|
||||||
// RFC 6637, Section 9.
|
|
||||||
func (pk *PublicKey) parseECDSA(r io.Reader) (err error) {
|
|
||||||
pk.oid = new(encoding.OID)
|
|
||||||
if _, err = pk.oid.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.p = new(encoding.MPI)
|
|
||||||
if _, err = pk.p.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
curveInfo := ecc.FindByOid(pk.oid)
|
|
||||||
if curveInfo == nil {
|
|
||||||
return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid))
|
|
||||||
}
|
|
||||||
|
|
||||||
c, ok := curveInfo.Curve.(ecc.ECDSACurve)
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid))
|
|
||||||
}
|
|
||||||
|
|
||||||
ecdsaKey := ecdsa.NewPublicKey(c)
|
|
||||||
err = ecdsaKey.UnmarshalPoint(pk.p.Bytes())
|
|
||||||
pk.PublicKey = ecdsaKey
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseECDH parses ECDH public key material from the given Reader. See
|
|
||||||
// RFC 6637, Section 9.
|
|
||||||
func (pk *PublicKey) parseECDH(r io.Reader) (err error) {
|
|
||||||
pk.oid = new(encoding.OID)
|
|
||||||
if _, err = pk.oid.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.p = new(encoding.MPI)
|
|
||||||
if _, err = pk.p.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk.kdf = new(encoding.OID)
|
|
||||||
if _, err = pk.kdf.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
curveInfo := ecc.FindByOid(pk.oid)
|
|
||||||
|
|
||||||
if curveInfo == nil {
|
|
||||||
return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid))
|
|
||||||
}
|
|
||||||
|
|
||||||
c, ok := curveInfo.Curve.(ecc.ECDHCurve)
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid))
|
|
||||||
}
|
|
||||||
|
|
||||||
if kdfLen := len(pk.kdf.Bytes()); kdfLen < 3 {
|
|
||||||
return errors.UnsupportedError("unsupported ECDH KDF length: " + strconv.Itoa(kdfLen))
|
|
||||||
}
|
|
||||||
if reserved := pk.kdf.Bytes()[0]; reserved != 0x01 {
|
|
||||||
return errors.UnsupportedError("unsupported KDF reserved field: " + strconv.Itoa(int(reserved)))
|
|
||||||
}
|
|
||||||
kdfHash, ok := algorithm.HashById[pk.kdf.Bytes()[1]]
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError("unsupported ECDH KDF hash: " + strconv.Itoa(int(pk.kdf.Bytes()[1])))
|
|
||||||
}
|
|
||||||
kdfCipher, ok := algorithm.CipherById[pk.kdf.Bytes()[2]]
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError("unsupported ECDH KDF cipher: " + strconv.Itoa(int(pk.kdf.Bytes()[2])))
|
|
||||||
}
|
|
||||||
|
|
||||||
ecdhKey := ecdh.NewPublicKey(c, kdfHash, kdfCipher)
|
|
||||||
err = ecdhKey.UnmarshalPoint(pk.p.Bytes())
|
|
||||||
pk.PublicKey = ecdhKey
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) {
|
|
||||||
pk.oid = new(encoding.OID)
|
|
||||||
if _, err = pk.oid.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
curveInfo := ecc.FindByOid(pk.oid)
|
|
||||||
if curveInfo == nil {
|
|
||||||
return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid))
|
|
||||||
}
|
|
||||||
|
|
||||||
c, ok := curveInfo.Curve.(ecc.EdDSACurve)
|
|
||||||
if !ok {
|
|
||||||
return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid))
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.p = new(encoding.MPI)
|
|
||||||
if _, err = pk.p.ReadFrom(r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pk.p.Bytes()) == 0 {
|
|
||||||
return errors.StructuralError("empty EdDSA public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub := eddsa.NewPublicKey(c)
|
|
||||||
|
|
||||||
switch flag := pk.p.Bytes()[0]; flag {
|
|
||||||
case 0x04:
|
|
||||||
// TODO: see _grcy_ecc_eddsa_ensure_compact in grcypt
|
|
||||||
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
|
|
||||||
case 0x40:
|
|
||||||
err = pub.UnmarshalPoint(pk.p.Bytes())
|
|
||||||
default:
|
|
||||||
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.PublicKey = pub
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeForHash serializes the PublicKey to w with the special packet
|
|
||||||
// header format needed for hashing.
|
|
||||||
func (pk *PublicKey) SerializeForHash(w io.Writer) error {
|
|
||||||
pk.SerializeSignaturePrefix(w)
|
|
||||||
return pk.serializeWithoutHeaders(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
|
|
||||||
// The prefix is used when calculating a signature over this public key. See
|
|
||||||
// RFC 4880, section 5.2.4.
|
|
||||||
func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) {
|
|
||||||
var pLength = pk.algorithmSpecificByteCount()
|
|
||||||
if pk.Version == 5 {
|
|
||||||
pLength += 10 // version, timestamp (4), algorithm, key octet count (4).
|
|
||||||
w.Write([]byte{
|
|
||||||
0x9A,
|
|
||||||
byte(pLength >> 24),
|
|
||||||
byte(pLength >> 16),
|
|
||||||
byte(pLength >> 8),
|
|
||||||
byte(pLength),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pLength += 6
|
|
||||||
w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) Serialize(w io.Writer) (err error) {
|
|
||||||
length := 6 // 6 byte header
|
|
||||||
length += pk.algorithmSpecificByteCount()
|
|
||||||
if pk.Version == 5 {
|
|
||||||
length += 4 // octet key count
|
|
||||||
}
|
|
||||||
packetType := packetTypePublicKey
|
|
||||||
if pk.IsSubkey {
|
|
||||||
packetType = packetTypePublicSubkey
|
|
||||||
}
|
|
||||||
err = serializeHeader(w, packetType, length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return pk.serializeWithoutHeaders(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pk *PublicKey) algorithmSpecificByteCount() int {
|
|
||||||
length := 0
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
length += int(pk.n.EncodedLength())
|
|
||||||
length += int(pk.e.EncodedLength())
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
length += int(pk.p.EncodedLength())
|
|
||||||
length += int(pk.q.EncodedLength())
|
|
||||||
length += int(pk.g.EncodedLength())
|
|
||||||
length += int(pk.y.EncodedLength())
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
length += int(pk.p.EncodedLength())
|
|
||||||
length += int(pk.g.EncodedLength())
|
|
||||||
length += int(pk.y.EncodedLength())
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
length += int(pk.oid.EncodedLength())
|
|
||||||
length += int(pk.p.EncodedLength())
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
length += int(pk.oid.EncodedLength())
|
|
||||||
length += int(pk.p.EncodedLength())
|
|
||||||
length += int(pk.kdf.EncodedLength())
|
|
||||||
case PubKeyAlgoEdDSA:
|
|
||||||
length += int(pk.oid.EncodedLength())
|
|
||||||
length += int(pk.p.EncodedLength())
|
|
||||||
default:
|
|
||||||
panic("unknown public key algorithm")
|
|
||||||
}
|
|
||||||
return length
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeWithoutHeaders marshals the PublicKey to w in the form of an
|
|
||||||
// OpenPGP public key packet, not including the packet header.
|
|
||||||
func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
|
|
||||||
t := uint32(pk.CreationTime.Unix())
|
|
||||||
if _, err = w.Write([]byte{
|
|
||||||
byte(pk.Version),
|
|
||||||
byte(t >> 24), byte(t >> 16), byte(t >> 8), byte(t),
|
|
||||||
byte(pk.PubKeyAlgo),
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk.Version == 5 {
|
|
||||||
n := pk.algorithmSpecificByteCount()
|
|
||||||
if _, err = w.Write([]byte{
|
|
||||||
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
if _, err = w.Write(pk.n.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(pk.e.EncodedBytes())
|
|
||||||
return
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
if _, err = w.Write(pk.p.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = w.Write(pk.q.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = w.Write(pk.g.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(pk.y.EncodedBytes())
|
|
||||||
return
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
if _, err = w.Write(pk.p.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = w.Write(pk.g.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(pk.y.EncodedBytes())
|
|
||||||
return
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
if _, err = w.Write(pk.oid.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(pk.p.EncodedBytes())
|
|
||||||
return
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
if _, err = w.Write(pk.oid.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = w.Write(pk.p.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(pk.kdf.EncodedBytes())
|
|
||||||
return
|
|
||||||
case PubKeyAlgoEdDSA:
|
|
||||||
if _, err = w.Write(pk.oid.EncodedBytes()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(pk.p.EncodedBytes())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return errors.InvalidArgumentError("bad public-key algorithm")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanSign returns true iff this public key can generate signatures
|
|
||||||
func (pk *PublicKey) CanSign() bool {
|
|
||||||
return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal && pk.PubKeyAlgo != PubKeyAlgoECDH
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, of the data hashed into signed. signed is mutated by this call.
|
|
||||||
func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) {
|
|
||||||
if !pk.CanSign() {
|
|
||||||
return errors.InvalidArgumentError("public key cannot generate signatures")
|
|
||||||
}
|
|
||||||
if sig.Version == 5 && (sig.SigType == 0x00 || sig.SigType == 0x01) {
|
|
||||||
sig.AddMetadataToHashSuffix()
|
|
||||||
}
|
|
||||||
signed.Write(sig.HashSuffix)
|
|
||||||
hashBytes := signed.Sum(nil)
|
|
||||||
if sig.Version == 5 && (hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1]) {
|
|
||||||
return errors.SignatureError("hash tag doesn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk.PubKeyAlgo != sig.PubKeyAlgo {
|
|
||||||
return errors.InvalidArgumentError("public key and signature use different algorithms")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
|
||||||
rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey)
|
|
||||||
err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.Bytes()))
|
|
||||||
if err != nil {
|
|
||||||
return errors.SignatureError("RSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
|
|
||||||
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
|
|
||||||
subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
|
|
||||||
if len(hashBytes) > subgroupSize {
|
|
||||||
hashBytes = hashBytes[:subgroupSize]
|
|
||||||
}
|
|
||||||
if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.Bytes()), new(big.Int).SetBytes(sig.DSASigS.Bytes())) {
|
|
||||||
return errors.SignatureError("DSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
ecdsaPublicKey := pk.PublicKey.(*ecdsa.PublicKey)
|
|
||||||
if !ecdsa.Verify(ecdsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.ECDSASigR.Bytes()), new(big.Int).SetBytes(sig.ECDSASigS.Bytes())) {
|
|
||||||
return errors.SignatureError("ECDSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case PubKeyAlgoEdDSA:
|
|
||||||
eddsaPublicKey := pk.PublicKey.(*eddsa.PublicKey)
|
|
||||||
if !eddsa.Verify(eddsaPublicKey, hashBytes, sig.EdDSASigR.Bytes(), sig.EdDSASigS.Bytes()) {
|
|
||||||
return errors.SignatureError("EdDSA verification failure")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return errors.SignatureError("Unsupported public key algorithm used in signature")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keySignatureHash returns a Hash of the message that needs to be signed for
|
|
||||||
// pk to assert a subkey relationship to signed.
|
|
||||||
func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
return nil, errors.UnsupportedError("hash function")
|
|
||||||
}
|
|
||||||
h = hashFunc.New()
|
|
||||||
|
|
||||||
// RFC 4880, section 5.2.4
|
|
||||||
err = pk.SerializeForHash(h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = signed.SerializeForHash(h)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyKeySignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, of signed.
|
|
||||||
func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error {
|
|
||||||
h, err := keySignatureHash(pk, signed, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = pk.VerifySignature(h, sig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig.FlagSign {
|
|
||||||
// Signing subkeys must be cross-signed. See
|
|
||||||
// https://www.gnupg.org/faq/subkey-cross-certify.html.
|
|
||||||
if sig.EmbeddedSignature == nil {
|
|
||||||
return errors.StructuralError("signing subkey is missing cross-signature")
|
|
||||||
}
|
|
||||||
// Verify the cross-signature. This is calculated over the same
|
|
||||||
// data as the main signature, so we cannot just recursively
|
|
||||||
// call signed.VerifyKeySignature(...)
|
|
||||||
if h, err = keySignatureHash(pk, signed, sig.EmbeddedSignature.Hash); err != nil {
|
|
||||||
return errors.StructuralError("error while hashing for cross-signature: " + err.Error())
|
|
||||||
}
|
|
||||||
if err := signed.VerifySignature(h, sig.EmbeddedSignature); err != nil {
|
|
||||||
return errors.StructuralError("error while verifying cross-signature: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
return nil, errors.UnsupportedError("hash function")
|
|
||||||
}
|
|
||||||
h = hashFunc.New()
|
|
||||||
|
|
||||||
// RFC 4880, section 5.2.4
|
|
||||||
err = pk.SerializeForHash(h)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyRevocationSignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key.
|
|
||||||
func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) {
|
|
||||||
h, err := keyRevocationHash(pk, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pk.VerifySignature(h, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySubkeyRevocationSignature returns nil iff sig is a valid subkey revocation signature,
|
|
||||||
// made by this public key, of signed.
|
|
||||||
func (pk *PublicKey) VerifySubkeyRevocationSignature(sig *Signature, signed *PublicKey) (err error) {
|
|
||||||
h, err := keySignatureHash(pk, signed, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pk.VerifySignature(h, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userIdSignatureHash returns a Hash of the message that needs to be signed
|
|
||||||
// to assert that pk is a valid key for id.
|
|
||||||
func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
return nil, errors.UnsupportedError("hash function")
|
|
||||||
}
|
|
||||||
h = hashFunc.New()
|
|
||||||
|
|
||||||
// RFC 4880, section 5.2.4
|
|
||||||
pk.SerializeSignaturePrefix(h)
|
|
||||||
pk.serializeWithoutHeaders(h)
|
|
||||||
|
|
||||||
var buf [5]byte
|
|
||||||
buf[0] = 0xb4
|
|
||||||
buf[1] = byte(len(id) >> 24)
|
|
||||||
buf[2] = byte(len(id) >> 16)
|
|
||||||
buf[3] = byte(len(id) >> 8)
|
|
||||||
buf[4] = byte(len(id))
|
|
||||||
h.Write(buf[:])
|
|
||||||
h.Write([]byte(id))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this
|
|
||||||
// public key, that id is the identity of pub.
|
|
||||||
func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) {
|
|
||||||
h, err := userIdSignatureHash(id, pub, sig.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pk.VerifySignature(h, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyIdString returns the public key's fingerprint in capital hex
|
|
||||||
// (e.g. "6C7EE1B8621CC013").
|
|
||||||
func (pk *PublicKey) KeyIdString() string {
|
|
||||||
return fmt.Sprintf("%X", pk.Fingerprint[12:20])
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyIdShortString returns the short form of public key's fingerprint
|
|
||||||
// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
|
|
||||||
func (pk *PublicKey) KeyIdShortString() string {
|
|
||||||
return fmt.Sprintf("%X", pk.Fingerprint[16:20])
|
|
||||||
}
|
|
||||||
|
|
||||||
// BitLength returns the bit length for the given public key.
|
|
||||||
func (pk *PublicKey) BitLength() (bitLength uint16, err error) {
|
|
||||||
switch pk.PubKeyAlgo {
|
|
||||||
case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
|
|
||||||
bitLength = pk.n.BitLength()
|
|
||||||
case PubKeyAlgoDSA:
|
|
||||||
bitLength = pk.p.BitLength()
|
|
||||||
case PubKeyAlgoElGamal:
|
|
||||||
bitLength = pk.p.BitLength()
|
|
||||||
case PubKeyAlgoECDSA:
|
|
||||||
bitLength = pk.p.BitLength()
|
|
||||||
case PubKeyAlgoECDH:
|
|
||||||
bitLength = pk.p.BitLength()
|
|
||||||
case PubKeyAlgoEdDSA:
|
|
||||||
bitLength = pk.p.BitLength()
|
|
||||||
default:
|
|
||||||
err = errors.InvalidArgumentError("bad public-key algorithm")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyExpired returns whether sig is a self-signature of a key that has
|
|
||||||
// expired or is created in the future.
|
|
||||||
func (pk *PublicKey) KeyExpired(sig *Signature, currentTime time.Time) bool {
|
|
||||||
if pk.CreationTime.After(currentTime) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if sig.KeyLifetimeSecs == nil || *sig.KeyLifetimeSecs == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
expiry := pk.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second)
|
|
||||||
return currentTime.After(expiry)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package packet
|
|
||||||
|
|
||||||
const rsaFingerprintHex = "5fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb"
|
|
||||||
|
|
||||||
const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001"
|
|
||||||
|
|
||||||
const dsaFingerprintHex = "eece4c094db002103714c63c8e8fbe54062f19ed"
|
|
||||||
|
|
||||||
const dsaPkDataHex = "9901a2044d432f89110400cd581334f0d7a1e1bdc8b9d6d8c0baf68793632735d2bb0903224cbaa1dfbf35a60ee7a13b92643421e1eb41aa8d79bea19a115a677f6b8ba3c7818ce53a6c2a24a1608bd8b8d6e55c5090cbde09dd26e356267465ae25e69ec8bdd57c7bbb2623e4d73336f73a0a9098f7f16da2e25252130fd694c0e8070c55a812a423ae7f00a0ebf50e70c2f19c3520a551bd4b08d30f23530d3d03ff7d0bf4a53a64a09dc5e6e6e35854b7d70c882b0c60293401958b1bd9e40abec3ea05ba87cf64899299d4bd6aa7f459c201d3fbbd6c82004bdc5e8a9eb8082d12054cc90fa9d4ec251a843236a588bf49552441817436c4f43326966fe85447d4e6d0acf8fa1ef0f014730770603ad7634c3088dc52501c237328417c31c89ed70400b2f1a98b0bf42f11fefc430704bebbaa41d9f355600c3facee1e490f64208e0e094ea55e3a598a219a58500bf78ac677b670a14f4e47e9cf8eab4f368cc1ddcaa18cc59309d4cc62dd4f680e73e6cc3e1ce87a84d0925efbcb26c575c093fc42eecf45135fabf6403a25c2016e1774c0484e440a18319072c617cc97ac0a3bb0"
|
|
||||||
|
|
||||||
const ecdsaFingerprintHex = "9892270b38b8980b05c8d56d43fe956c542ca00b"
|
|
||||||
|
|
||||||
const ecdsaPkDataHex = "9893045071c29413052b8104002304230401f4867769cedfa52c325018896245443968e52e51d0c2df8d939949cb5b330f2921711fbee1c9b9dddb95d15cb0255e99badeddda7cc23d9ddcaacbc290969b9f24019375d61c2e4e3b36953a28d8b2bc95f78c3f1d592fb24499be348656a7b17e3963187b4361afe497bc5f9f81213f04069f8e1fb9e6a6290ae295ca1a92b894396cb4"
|
|
||||||
|
|
||||||
const ecdhFingerprintHex = "722354df2475a42164d1d49faa8b938f9a201946"
|
|
||||||
|
|
||||||
const ecdhPkDataHex = "b90073044d53059212052b810400220303042faa84024a20b6735c4897efa5bfb41bf85b7eefeab5ca0cb9ffc8ea04a46acb25534a577694f9e25340a4ab5223a9dd1eda530c8aa2e6718db10d7e672558c7736fe09369ea5739a2a3554bf16d41faa50562f11c6d39bbd5dffb6b9a9ec91803010909"
|
|
||||||
|
|
||||||
const eddsaFingerprintHex = "b2d5e5ec0e6deca6bc8eeeb00907e75e1dd99ad8"
|
|
||||||
|
|
||||||
const eddsaPkDataHex = "98330456e2132b16092b06010401da470f01010740bbda39266affa511a8c2d02edf690fb784b0499c4406185811a163539ef11dc1b41d74657374696e67203c74657374696e674074657374696e672e636f6d3e8879041316080021050256e2132b021b03050b09080702061508090a0b020416020301021e01021780000a09100907e75e1dd99ad86d0c00fe39d2008359352782bc9b61ac382584cd8eff3f57a18c2287e3afeeb05d1f04ba00fe2d0bc1ddf3ff8adb9afa3e7d9287244b4ec567f3db4d60b74a9b5465ed528203"
|
|
||||||
|
|
||||||
// Source: https://sites.google.com/site/brainhub/pgpecckeys#TOC-ECC-NIST-P-384-key
|
|
||||||
const ecc384PubHex = `99006f044d53059213052b81040022030304f6b8c5aced5b84ef9f4a209db2e4a9dfb70d28cb8c10ecd57674a9fa5a67389942b62d5e51367df4c7bfd3f8e500feecf07ed265a621a8ebbbe53e947ec78c677eba143bd1533c2b350e1c29f82313e1e1108eba063be1e64b10e6950e799c2db42465635f6473615f64685f333834203c6f70656e70677040627261696e6875622e6f72673e8900cb04101309005305024d530592301480000000002000077072656665727265642d656d61696c2d656e636f64696e67407067702e636f6d7067706d696d65040b090807021901051b03000000021602051e010000000415090a08000a0910098033880f54719fca2b0180aa37350968bd5f115afd8ce7bc7b103822152dbff06d0afcda835329510905b98cb469ba208faab87c7412b799e7b633017f58364ea480e8a1a3f253a0c5f22c446e8be9a9fce6210136ee30811abbd49139de28b5bdf8dc36d06ae748579e9ff503b90073044d53059212052b810400220303042faa84024a20b6735c4897efa5bfb41bf85b7eefeab5ca0cb9ffc8ea04a46acb25534a577694f9e25340a4ab5223a9dd1eda530c8aa2e6718db10d7e672558c7736fe09369ea5739a2a3554bf16d41faa50562f11c6d39bbd5dffb6b9a9ec9180301090989008404181309000c05024d530592051b0c000000000a0910098033880f54719f80970180eee7a6d8fcee41ee4f9289df17f9bcf9d955dca25c583b94336f3a2b2d4986dc5cf417b8d2dc86f741a9e1a6d236c0e3017d1c76575458a0cfb93ae8a2b274fcc65ceecd7a91eec83656ba13219969f06945b48c56bd04152c3a0553c5f2f4bd1267`
|
|
@ -1,86 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reader reads packets from an io.Reader and allows packets to be 'unread' so
|
|
||||||
// that they result from the next call to Next.
|
|
||||||
type Reader struct {
|
|
||||||
q []Packet
|
|
||||||
readers []io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// New io.Readers are pushed when a compressed or encrypted packet is processed
|
|
||||||
// and recursively treated as a new source of packets. However, a carefully
|
|
||||||
// crafted packet can trigger an infinite recursive sequence of packets. See
|
|
||||||
// http://mumble.net/~campbell/misc/pgp-quine
|
|
||||||
// https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-4402
|
|
||||||
// This constant limits the number of recursive packets that may be pushed.
|
|
||||||
const maxReaders = 32
|
|
||||||
|
|
||||||
// Next returns the most recently unread Packet, or reads another packet from
|
|
||||||
// the top-most io.Reader. Unknown packet types are skipped.
|
|
||||||
func (r *Reader) Next() (p Packet, err error) {
|
|
||||||
if len(r.q) > 0 {
|
|
||||||
p = r.q[len(r.q)-1]
|
|
||||||
r.q = r.q[:len(r.q)-1]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(r.readers) > 0 {
|
|
||||||
p, err = Read(r.readers[len(r.readers)-1])
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err == io.EOF {
|
|
||||||
r.readers = r.readers[:len(r.readers)-1]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO: Add strict mode that rejects unknown packets, instead of ignoring them.
|
|
||||||
if _, ok := err.(errors.UnknownPacketTypeError); ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := err.(errors.UnsupportedError); ok {
|
|
||||||
switch p.(type) {
|
|
||||||
case *SymmetricallyEncrypted, *AEADEncrypted, *Compressed, *LiteralData:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push causes the Reader to start reading from a new io.Reader. When an EOF
|
|
||||||
// error is seen from the new io.Reader, it is popped and the Reader continues
|
|
||||||
// to read from the next most recent io.Reader. Push returns a StructuralError
|
|
||||||
// if pushing the reader would exceed the maximum recursion level, otherwise it
|
|
||||||
// returns nil.
|
|
||||||
func (r *Reader) Push(reader io.Reader) (err error) {
|
|
||||||
if len(r.readers) >= maxReaders {
|
|
||||||
return errors.StructuralError("too many layers of packets")
|
|
||||||
}
|
|
||||||
r.readers = append(r.readers, reader)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unread causes the given Packet to be returned from the next call to Next.
|
|
||||||
func (r *Reader) Unread(p Packet) {
|
|
||||||
r.q = append(r.q, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReader(r io.Reader) *Reader {
|
|
||||||
return &Reader{
|
|
||||||
q: nil,
|
|
||||||
readers: []io.Reader{r},
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
276
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetric_key_encrypted.go
generated
vendored
276
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetric_key_encrypted.go
generated
vendored
@ -1,276 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is the largest session key that we'll support. Since at most 256-bit cipher
|
|
||||||
// is supported in OpenPGP, this is large enough to contain also the auth tag.
|
|
||||||
const maxSessionKeySizeInBytes = 64
|
|
||||||
|
|
||||||
// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
|
|
||||||
// 4880, section 5.3.
|
|
||||||
type SymmetricKeyEncrypted struct {
|
|
||||||
Version int
|
|
||||||
CipherFunc CipherFunction
|
|
||||||
Mode AEADMode
|
|
||||||
s2k func(out, in []byte)
|
|
||||||
iv []byte
|
|
||||||
encryptedKey []byte // Contains also the authentication tag for AEAD
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse parses an SymmetricKeyEncrypted packet as specified in
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#name-symmetric-key-encrypted-ses
|
|
||||||
func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
|
|
||||||
var buf [1]byte
|
|
||||||
|
|
||||||
// Version
|
|
||||||
if _, err := readFull(r, buf[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ske.Version = int(buf[0])
|
|
||||||
if ske.Version != 4 && ske.Version != 5 {
|
|
||||||
return errors.UnsupportedError("unknown SymmetricKeyEncrypted version")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cipher function
|
|
||||||
if _, err := readFull(r, buf[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ske.CipherFunc = CipherFunction(buf[0])
|
|
||||||
if !ske.CipherFunc.IsSupported() {
|
|
||||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
if ske.Version == 5 {
|
|
||||||
// AEAD mode
|
|
||||||
if _, err := readFull(r, buf[:]); err != nil {
|
|
||||||
return errors.StructuralError("cannot read AEAD octet from packet")
|
|
||||||
}
|
|
||||||
ske.Mode = AEADMode(buf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if ske.s2k, err = s2k.Parse(r); err != nil {
|
|
||||||
if _, ok := err.(errors.ErrDummyPrivateKey); ok {
|
|
||||||
return errors.UnsupportedError("missing key GNU extension in session key")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ske.Version == 5 {
|
|
||||||
// AEAD IV
|
|
||||||
iv := make([]byte, ske.Mode.IvLength())
|
|
||||||
_, err := readFull(r, iv)
|
|
||||||
if err != nil {
|
|
||||||
return errors.StructuralError("cannot read AEAD IV")
|
|
||||||
}
|
|
||||||
|
|
||||||
ske.iv = iv
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedKey := make([]byte, maxSessionKeySizeInBytes)
|
|
||||||
// The session key may follow. We just have to try and read to find
|
|
||||||
// out. If it exists then we limit it to maxSessionKeySizeInBytes.
|
|
||||||
n, err := readFull(r, encryptedKey)
|
|
||||||
if err != nil && err != io.ErrUnexpectedEOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n != 0 {
|
|
||||||
if n == maxSessionKeySizeInBytes {
|
|
||||||
return errors.UnsupportedError("oversized encrypted session key")
|
|
||||||
}
|
|
||||||
ske.encryptedKey = encryptedKey[:n]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt attempts to decrypt an encrypted session key and returns the key and
|
|
||||||
// the cipher to use when decrypting a subsequent Symmetrically Encrypted Data
|
|
||||||
// packet.
|
|
||||||
func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) ([]byte, CipherFunction, error) {
|
|
||||||
key := make([]byte, ske.CipherFunc.KeySize())
|
|
||||||
ske.s2k(key, passphrase)
|
|
||||||
if len(ske.encryptedKey) == 0 {
|
|
||||||
return key, ske.CipherFunc, nil
|
|
||||||
}
|
|
||||||
switch ske.Version {
|
|
||||||
case 4:
|
|
||||||
plaintextKey, cipherFunc, err := ske.decryptV4(key)
|
|
||||||
return plaintextKey, cipherFunc, err
|
|
||||||
case 5:
|
|
||||||
plaintextKey, err := ske.decryptV5(key)
|
|
||||||
return plaintextKey, CipherFunction(0), err
|
|
||||||
}
|
|
||||||
err := errors.UnsupportedError("unknown SymmetricKeyEncrypted version")
|
|
||||||
return nil, CipherFunction(0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ske *SymmetricKeyEncrypted) decryptV4(key []byte) ([]byte, CipherFunction, error) {
|
|
||||||
// the IV is all zeros
|
|
||||||
iv := make([]byte, ske.CipherFunc.blockSize())
|
|
||||||
c := cipher.NewCFBDecrypter(ske.CipherFunc.new(key), iv)
|
|
||||||
plaintextKey := make([]byte, len(ske.encryptedKey))
|
|
||||||
c.XORKeyStream(plaintextKey, ske.encryptedKey)
|
|
||||||
cipherFunc := CipherFunction(plaintextKey[0])
|
|
||||||
if cipherFunc.blockSize() == 0 {
|
|
||||||
return nil, ske.CipherFunc, errors.UnsupportedError(
|
|
||||||
"unknown cipher: " + strconv.Itoa(int(cipherFunc)))
|
|
||||||
}
|
|
||||||
plaintextKey = plaintextKey[1:]
|
|
||||||
if len(plaintextKey) != cipherFunc.KeySize() {
|
|
||||||
return nil, cipherFunc, errors.StructuralError(
|
|
||||||
"length of decrypted key not equal to cipher keysize")
|
|
||||||
}
|
|
||||||
return plaintextKey, cipherFunc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) {
|
|
||||||
adata := []byte{0xc3, byte(5), byte(ske.CipherFunc), byte(ske.Mode)}
|
|
||||||
aead := getEncryptedKeyAeadInstance(ske.CipherFunc, ske.Mode, key, adata)
|
|
||||||
|
|
||||||
plaintextKey, err := aead.Open(nil, ske.iv, ske.encryptedKey, adata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return plaintextKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w.
|
|
||||||
// The packet contains a random session key, encrypted by a key derived from
|
|
||||||
// the given passphrase. The session key is returned and must be passed to
|
|
||||||
// SerializeSymmetricallyEncrypted.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) {
|
|
||||||
cipherFunc := config.Cipher()
|
|
||||||
|
|
||||||
sessionKey := make([]byte, cipherFunc.KeySize())
|
|
||||||
_, err = io.ReadFull(config.Random(), sessionKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SerializeSymmetricKeyEncryptedReuseKey(w, sessionKey, passphrase, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key = sessionKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeSymmetricKeyEncryptedReuseKey serializes a symmetric key packet to w.
|
|
||||||
// The packet contains the given session key, encrypted by a key derived from
|
|
||||||
// the given passphrase. The returned session key must be passed to
|
|
||||||
// SerializeSymmetricallyEncrypted.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, config *Config) (err error) {
|
|
||||||
var version int
|
|
||||||
if config.AEAD() != nil {
|
|
||||||
version = 5
|
|
||||||
} else {
|
|
||||||
version = 4
|
|
||||||
}
|
|
||||||
cipherFunc := config.Cipher()
|
|
||||||
// cipherFunc must be AES
|
|
||||||
if !cipherFunc.IsSupported() || cipherFunc < CipherAES128 || cipherFunc > CipherAES256 {
|
|
||||||
return errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(cipherFunc)))
|
|
||||||
}
|
|
||||||
|
|
||||||
keySize := cipherFunc.KeySize()
|
|
||||||
s2kBuf := new(bytes.Buffer)
|
|
||||||
keyEncryptingKey := make([]byte, keySize)
|
|
||||||
// s2k.Serialize salts and stretches the passphrase, and writes the
|
|
||||||
// resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
|
|
||||||
err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, config.S2K())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s2kBytes := s2kBuf.Bytes()
|
|
||||||
|
|
||||||
var packetLength int
|
|
||||||
switch version {
|
|
||||||
case 4:
|
|
||||||
packetLength = 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
|
|
||||||
case 5:
|
|
||||||
ivLen := config.AEAD().Mode().IvLength()
|
|
||||||
tagLen := config.AEAD().Mode().TagLength()
|
|
||||||
packetLength = 3 + len(s2kBytes) + ivLen + keySize + tagLen
|
|
||||||
}
|
|
||||||
err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symmetric Key Encrypted Version
|
|
||||||
buf := []byte{byte(version)}
|
|
||||||
|
|
||||||
// Cipher function
|
|
||||||
buf = append(buf, byte(cipherFunc))
|
|
||||||
|
|
||||||
if version == 5 {
|
|
||||||
// AEAD mode
|
|
||||||
buf = append(buf, byte(config.AEAD().Mode()))
|
|
||||||
}
|
|
||||||
_, err = w.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(s2kBytes)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch version {
|
|
||||||
case 4:
|
|
||||||
iv := make([]byte, cipherFunc.blockSize())
|
|
||||||
c := cipher.NewCFBEncrypter(cipherFunc.new(keyEncryptingKey), iv)
|
|
||||||
encryptedCipherAndKey := make([]byte, keySize+1)
|
|
||||||
c.XORKeyStream(encryptedCipherAndKey, buf[1:])
|
|
||||||
c.XORKeyStream(encryptedCipherAndKey[1:], sessionKey)
|
|
||||||
_, err = w.Write(encryptedCipherAndKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case 5:
|
|
||||||
mode := config.AEAD().Mode()
|
|
||||||
adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)}
|
|
||||||
aead := getEncryptedKeyAeadInstance(cipherFunc, mode, keyEncryptingKey, adata)
|
|
||||||
|
|
||||||
// Sample iv using random reader
|
|
||||||
iv := make([]byte, config.AEAD().Mode().IvLength())
|
|
||||||
_, err = io.ReadFull(config.Random(), iv)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Seal and write (encryptedData includes auth. tag)
|
|
||||||
|
|
||||||
encryptedData := aead.Seal(nil, iv, sessionKey, adata)
|
|
||||||
_, err = w.Write(iv)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = w.Write(encryptedData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEncryptedKeyAeadInstance(c CipherFunction, mode AEADMode, inputKey, associatedData []byte) (aead cipher.AEAD) {
|
|
||||||
blockCipher := c.new(inputKey)
|
|
||||||
return mode.new(blockCipher)
|
|
||||||
}
|
|
90
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go
generated
vendored
90
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go
generated
vendored
@ -1,90 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const aeadSaltSize = 32
|
|
||||||
|
|
||||||
// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
|
|
||||||
// encrypted Contents will consist of more OpenPGP packets. See RFC 4880,
|
|
||||||
// sections 5.7 and 5.13.
|
|
||||||
type SymmetricallyEncrypted struct {
|
|
||||||
Version int
|
|
||||||
Contents io.Reader // contains tag for version 2
|
|
||||||
IntegrityProtected bool // If true it is type 18 (with MDC or AEAD). False is packet type 9
|
|
||||||
|
|
||||||
// Specific to version 1
|
|
||||||
prefix []byte
|
|
||||||
|
|
||||||
// Specific to version 2
|
|
||||||
Cipher CipherFunction
|
|
||||||
Mode AEADMode
|
|
||||||
ChunkSizeByte byte
|
|
||||||
Salt [aeadSaltSize]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
symmetricallyEncryptedVersionMdc = 1
|
|
||||||
symmetricallyEncryptedVersionAead = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
|
|
||||||
if se.IntegrityProtected {
|
|
||||||
// See RFC 4880, section 5.13.
|
|
||||||
var buf [1]byte
|
|
||||||
_, err := readFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch buf[0] {
|
|
||||||
case symmetricallyEncryptedVersionMdc:
|
|
||||||
se.Version = symmetricallyEncryptedVersionMdc
|
|
||||||
case symmetricallyEncryptedVersionAead:
|
|
||||||
se.Version = symmetricallyEncryptedVersionAead
|
|
||||||
if err := se.parseAead(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
se.Contents = r
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt returns a ReadCloser, from which the decrypted Contents of the
|
|
||||||
// packet can be read. An incorrect key will only be detected after trying
|
|
||||||
// to decrypt the entire data.
|
|
||||||
func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
|
|
||||||
if se.Version == symmetricallyEncryptedVersionAead {
|
|
||||||
return se.decryptAead(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return se.decryptMdc(c, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
|
|
||||||
// to w and returns a WriteCloser to which the to-be-encrypted packets can be
|
|
||||||
// written.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, aeadSupported bool, cipherSuite CipherSuite, key []byte, config *Config) (Contents io.WriteCloser, err error) {
|
|
||||||
writeCloser := noOpCloser{w}
|
|
||||||
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedIntegrityProtected)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if aeadSupported {
|
|
||||||
return serializeSymmetricallyEncryptedAead(ciphertext, cipherSuite, config.AEADConfig.ChunkSizeByte(), config.Random(), key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return serializeSymmetricallyEncryptedMdc(ciphertext, c, key, config)
|
|
||||||
}
|
|
156
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go
generated
vendored
156
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go
generated
vendored
@ -1,156 +0,0 @@
|
|||||||
// Copyright 2023 Proton AG. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/sha256"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"golang.org/x/crypto/hkdf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// parseAead parses a V2 SEIPD packet (AEAD) as specified in
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
|
||||||
func (se *SymmetricallyEncrypted) parseAead(r io.Reader) error {
|
|
||||||
headerData := make([]byte, 3)
|
|
||||||
if n, err := io.ReadFull(r, headerData); n < 3 {
|
|
||||||
return errors.StructuralError("could not read aead header: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cipher
|
|
||||||
se.Cipher = CipherFunction(headerData[0])
|
|
||||||
// cipherFunc must have block size 16 to use AEAD
|
|
||||||
if se.Cipher.blockSize() != 16 {
|
|
||||||
return errors.UnsupportedError("invalid aead cipher: " + string(se.Cipher))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode
|
|
||||||
se.Mode = AEADMode(headerData[1])
|
|
||||||
if se.Mode.TagLength() == 0 {
|
|
||||||
return errors.UnsupportedError("unknown aead mode: " + string(se.Mode))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chunk size
|
|
||||||
se.ChunkSizeByte = headerData[2]
|
|
||||||
if se.ChunkSizeByte > 16 {
|
|
||||||
return errors.UnsupportedError("invalid aead chunk size byte: " + string(se.ChunkSizeByte))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Salt
|
|
||||||
if n, err := io.ReadFull(r, se.Salt[:]); n < aeadSaltSize {
|
|
||||||
return errors.StructuralError("could not read aead salt: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// associatedData for chunks: tag, version, cipher, mode, chunk size byte
|
|
||||||
func (se *SymmetricallyEncrypted) associatedData() []byte {
|
|
||||||
return []byte{
|
|
||||||
0xD2,
|
|
||||||
symmetricallyEncryptedVersionAead,
|
|
||||||
byte(se.Cipher),
|
|
||||||
byte(se.Mode),
|
|
||||||
se.ChunkSizeByte,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decryptAead decrypts a V2 SEIPD packet (AEAD) as specified in
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
|
||||||
func (se *SymmetricallyEncrypted) decryptAead(inputKey []byte) (io.ReadCloser, error) {
|
|
||||||
aead, nonce := getSymmetricallyEncryptedAeadInstance(se.Cipher, se.Mode, inputKey, se.Salt[:], se.associatedData())
|
|
||||||
|
|
||||||
// Carry the first tagLen bytes
|
|
||||||
tagLen := se.Mode.TagLength()
|
|
||||||
peekedBytes := make([]byte, tagLen)
|
|
||||||
n, err := io.ReadFull(se.Contents, peekedBytes)
|
|
||||||
if n < tagLen || (err != nil && err != io.EOF) {
|
|
||||||
return nil, errors.StructuralError("not enough data to decrypt:" + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &aeadDecrypter{
|
|
||||||
aeadCrypter: aeadCrypter{
|
|
||||||
aead: aead,
|
|
||||||
chunkSize: decodeAEADChunkSize(se.ChunkSizeByte),
|
|
||||||
initialNonce: nonce,
|
|
||||||
associatedData: se.associatedData(),
|
|
||||||
chunkIndex: make([]byte, 8),
|
|
||||||
packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected,
|
|
||||||
},
|
|
||||||
reader: se.Contents,
|
|
||||||
peekedBytes: peekedBytes,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeSymmetricallyEncryptedAead encrypts to a writer a V2 SEIPD packet (AEAD) as specified in
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
|
|
||||||
func serializeSymmetricallyEncryptedAead(ciphertext io.WriteCloser, cipherSuite CipherSuite, chunkSizeByte byte, rand io.Reader, inputKey []byte) (Contents io.WriteCloser, err error) {
|
|
||||||
// cipherFunc must have block size 16 to use AEAD
|
|
||||||
if cipherSuite.Cipher.blockSize() != 16 {
|
|
||||||
return nil, errors.InvalidArgumentError("invalid aead cipher function")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cipherSuite.Cipher.KeySize() != len(inputKey) {
|
|
||||||
return nil, errors.InvalidArgumentError("error in aead serialization: bad key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data for en/decryption: tag, version, cipher, aead mode, chunk size
|
|
||||||
prefix := []byte{
|
|
||||||
0xD2,
|
|
||||||
symmetricallyEncryptedVersionAead,
|
|
||||||
byte(cipherSuite.Cipher),
|
|
||||||
byte(cipherSuite.Mode),
|
|
||||||
chunkSizeByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write header (that correspond to prefix except first byte)
|
|
||||||
n, err := ciphertext.Write(prefix[1:])
|
|
||||||
if err != nil || n < 4 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random salt
|
|
||||||
salt := make([]byte, aeadSaltSize)
|
|
||||||
if _, err := rand.Read(salt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := ciphertext.Write(salt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
aead, nonce := getSymmetricallyEncryptedAeadInstance(cipherSuite.Cipher, cipherSuite.Mode, inputKey, salt, prefix)
|
|
||||||
|
|
||||||
return &aeadEncrypter{
|
|
||||||
aeadCrypter: aeadCrypter{
|
|
||||||
aead: aead,
|
|
||||||
chunkSize: decodeAEADChunkSize(chunkSizeByte),
|
|
||||||
associatedData: prefix,
|
|
||||||
chunkIndex: make([]byte, 8),
|
|
||||||
initialNonce: nonce,
|
|
||||||
packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected,
|
|
||||||
},
|
|
||||||
writer: ciphertext,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSymmetricallyEncryptedAeadInstance(c CipherFunction, mode AEADMode, inputKey, salt, associatedData []byte) (aead cipher.AEAD, nonce []byte) {
|
|
||||||
hkdfReader := hkdf.New(sha256.New, inputKey, salt, associatedData)
|
|
||||||
|
|
||||||
encryptionKey := make([]byte, c.KeySize())
|
|
||||||
_, _ = readFull(hkdfReader, encryptionKey)
|
|
||||||
|
|
||||||
// Last 64 bits of nonce are the counter
|
|
||||||
nonce = make([]byte, mode.IvLength()-8)
|
|
||||||
|
|
||||||
_, _ = readFull(hkdfReader, nonce)
|
|
||||||
|
|
||||||
blockCipher := c.new(encryptionKey)
|
|
||||||
aead = mode.new(blockCipher)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
256
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_mdc.go
generated
vendored
256
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_mdc.go
generated
vendored
@ -1,256 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/subtle"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// seMdcReader wraps an io.Reader with a no-op Close method.
|
|
||||||
type seMdcReader struct {
|
|
||||||
in io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ser seMdcReader) Read(buf []byte) (int, error) {
|
|
||||||
return ser.in.Read(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ser seMdcReader) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (se *SymmetricallyEncrypted) decryptMdc(c CipherFunction, key []byte) (io.ReadCloser, error) {
|
|
||||||
if !c.IsSupported() {
|
|
||||||
return nil, errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(c)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(key) != c.KeySize() {
|
|
||||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
if se.prefix == nil {
|
|
||||||
se.prefix = make([]byte, c.blockSize()+2)
|
|
||||||
_, err := readFull(se.Contents, se.prefix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if len(se.prefix) != c.blockSize()+2 {
|
|
||||||
return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
|
|
||||||
}
|
|
||||||
|
|
||||||
ocfbResync := OCFBResync
|
|
||||||
if se.IntegrityProtected {
|
|
||||||
// MDC packets use a different form of OCFB mode.
|
|
||||||
ocfbResync = OCFBNoResync
|
|
||||||
}
|
|
||||||
|
|
||||||
s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
|
|
||||||
|
|
||||||
plaintext := cipher.StreamReader{S: s, R: se.Contents}
|
|
||||||
|
|
||||||
if se.IntegrityProtected {
|
|
||||||
// IntegrityProtected packets have an embedded hash that we need to check.
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write(se.prefix)
|
|
||||||
return &seMDCReader{in: plaintext, h: h}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
|
|
||||||
return seMdcReader{plaintext}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
|
|
||||||
|
|
||||||
// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
|
|
||||||
// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
|
|
||||||
// MDC packet containing a hash of the previous Contents which is checked
|
|
||||||
// against the running hash. See RFC 4880, section 5.13.
|
|
||||||
type seMDCReader struct {
|
|
||||||
in io.Reader
|
|
||||||
h hash.Hash
|
|
||||||
trailer [mdcTrailerSize]byte
|
|
||||||
scratch [mdcTrailerSize]byte
|
|
||||||
trailerUsed int
|
|
||||||
error bool
|
|
||||||
eof bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
|
|
||||||
if ser.error {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ser.eof {
|
|
||||||
err = io.EOF
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we haven't yet filled the trailer buffer then we must do that
|
|
||||||
// first.
|
|
||||||
for ser.trailerUsed < mdcTrailerSize {
|
|
||||||
n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
|
|
||||||
ser.trailerUsed += n
|
|
||||||
if err == io.EOF {
|
|
||||||
if ser.trailerUsed != mdcTrailerSize {
|
|
||||||
n = 0
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
ser.error = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ser.eof = true
|
|
||||||
n = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
n = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a short read then we read into a temporary buffer and shift
|
|
||||||
// the data into the caller's buffer.
|
|
||||||
if len(buf) <= mdcTrailerSize {
|
|
||||||
n, err = readFull(ser.in, ser.scratch[:len(buf)])
|
|
||||||
copy(buf, ser.trailer[:n])
|
|
||||||
ser.h.Write(buf[:n])
|
|
||||||
copy(ser.trailer[:], ser.trailer[n:])
|
|
||||||
copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
|
|
||||||
if n < len(buf) {
|
|
||||||
ser.eof = true
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = ser.in.Read(buf[mdcTrailerSize:])
|
|
||||||
copy(buf, ser.trailer[:])
|
|
||||||
ser.h.Write(buf[:n])
|
|
||||||
copy(ser.trailer[:], buf[n:])
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
ser.eof = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a new-format packet tag byte for a type 19 (Integrity Protected) packet.
|
|
||||||
const mdcPacketTagByte = byte(0x80) | 0x40 | 19
|
|
||||||
|
|
||||||
func (ser *seMDCReader) Close() error {
|
|
||||||
if ser.error {
|
|
||||||
return errors.ErrMDCMissing
|
|
||||||
}
|
|
||||||
|
|
||||||
for !ser.eof {
|
|
||||||
// We haven't seen EOF so we need to read to the end
|
|
||||||
var buf [1024]byte
|
|
||||||
_, err := ser.Read(buf[:])
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return errors.ErrMDCMissing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ser.h.Write(ser.trailer[:2])
|
|
||||||
|
|
||||||
final := ser.h.Sum(nil)
|
|
||||||
if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
|
|
||||||
return errors.ErrMDCHashMismatch
|
|
||||||
}
|
|
||||||
// The hash already includes the MDC header, but we still check its value
|
|
||||||
// to confirm encryption correctness
|
|
||||||
if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
|
|
||||||
return errors.ErrMDCMissing
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// An seMDCWriter writes through to an io.WriteCloser while maintains a running
|
|
||||||
// hash of the data written. On close, it emits an MDC packet containing the
|
|
||||||
// running hash.
|
|
||||||
type seMDCWriter struct {
|
|
||||||
w io.WriteCloser
|
|
||||||
h hash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
|
|
||||||
w.h.Write(buf)
|
|
||||||
return w.w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *seMDCWriter) Close() (err error) {
|
|
||||||
var buf [mdcTrailerSize]byte
|
|
||||||
|
|
||||||
buf[0] = mdcPacketTagByte
|
|
||||||
buf[1] = sha1.Size
|
|
||||||
w.h.Write(buf[:2])
|
|
||||||
digest := w.h.Sum(nil)
|
|
||||||
copy(buf[2:], digest)
|
|
||||||
|
|
||||||
_, err = w.w.Write(buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return w.w.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
|
|
||||||
type noOpCloser struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c noOpCloser) Write(data []byte) (n int, err error) {
|
|
||||||
return c.w.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c noOpCloser) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeSymmetricallyEncryptedMdc(ciphertext io.WriteCloser, c CipherFunction, key []byte, config *Config) (Contents io.WriteCloser, err error) {
|
|
||||||
// Disallow old cipher suites
|
|
||||||
if !c.IsSupported() || c < CipherAES128 {
|
|
||||||
return nil, errors.InvalidArgumentError("invalid mdc cipher function")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.KeySize() != len(key) {
|
|
||||||
return nil, errors.InvalidArgumentError("error in mdc serialization: bad key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersionMdc})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
block := c.new(key)
|
|
||||||
blockSize := block.BlockSize()
|
|
||||||
iv := make([]byte, blockSize)
|
|
||||||
_, err = config.Random().Read(iv)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync)
|
|
||||||
_, err = ciphertext.Write(prefix)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
plaintext := cipher.StreamWriter{S: s, W: ciphertext}
|
|
||||||
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write(iv)
|
|
||||||
h.Write(iv[blockSize-2:])
|
|
||||||
Contents = &seMDCWriter{w: plaintext, h: h}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"image"
|
|
||||||
"image/jpeg"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
const UserAttrImageSubpacket = 1
|
|
||||||
|
|
||||||
// UserAttribute is capable of storing other types of data about a user
|
|
||||||
// beyond name, email and a text comment. In practice, user attributes are typically used
|
|
||||||
// to store a signed thumbnail photo JPEG image of the user.
|
|
||||||
// See RFC 4880, section 5.12.
|
|
||||||
type UserAttribute struct {
|
|
||||||
Contents []*OpaqueSubpacket
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUserAttributePhoto creates a user attribute packet
|
|
||||||
// containing the given images.
|
|
||||||
func NewUserAttributePhoto(photos ...image.Image) (uat *UserAttribute, err error) {
|
|
||||||
uat = new(UserAttribute)
|
|
||||||
for _, photo := range photos {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
// RFC 4880, Section 5.12.1.
|
|
||||||
data := []byte{
|
|
||||||
0x10, 0x00, // Little-endian image header length (16 bytes)
|
|
||||||
0x01, // Image header version 1
|
|
||||||
0x01, // JPEG
|
|
||||||
0, 0, 0, 0, // 12 reserved octets, must be all zero.
|
|
||||||
0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0}
|
|
||||||
if _, err = buf.Write(data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = jpeg.Encode(&buf, photo, nil); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
lengthBuf := make([]byte, 5)
|
|
||||||
n := serializeSubpacketLength(lengthBuf, len(buf.Bytes())+1)
|
|
||||||
lengthBuf = lengthBuf[:n]
|
|
||||||
|
|
||||||
uat.Contents = append(uat.Contents, &OpaqueSubpacket{
|
|
||||||
SubType: UserAttrImageSubpacket,
|
|
||||||
EncodedLength: lengthBuf,
|
|
||||||
Contents: buf.Bytes(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUserAttribute creates a new user attribute packet containing the given subpackets.
|
|
||||||
func NewUserAttribute(contents ...*OpaqueSubpacket) *UserAttribute {
|
|
||||||
return &UserAttribute{Contents: contents}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uat *UserAttribute) parse(r io.Reader) (err error) {
|
|
||||||
// RFC 4880, section 5.13
|
|
||||||
b, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uat.Contents, err = OpaqueSubpackets(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize marshals the user attribute to w in the form of an OpenPGP packet, including
|
|
||||||
// header.
|
|
||||||
func (uat *UserAttribute) Serialize(w io.Writer) (err error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for _, sp := range uat.Contents {
|
|
||||||
err = sp.Serialize(&buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = serializeHeader(w, packetTypeUserAttribute, buf.Len()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(buf.Bytes())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImageData returns zero or more byte slices, each containing
|
|
||||||
// JPEG File Interchange Format (JFIF), for each photo in the
|
|
||||||
// user attribute packet.
|
|
||||||
func (uat *UserAttribute) ImageData() (imageData [][]byte) {
|
|
||||||
for _, sp := range uat.Contents {
|
|
||||||
if sp.SubType == UserAttrImageSubpacket && len(sp.Contents) > 16 {
|
|
||||||
imageData = append(imageData, sp.Contents[16:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserId contains text that is intended to represent the name and email
|
|
||||||
// address of the key holder. See RFC 4880, section 5.11. By convention, this
|
|
||||||
// takes the form "Full Name (Comment) <email@example.com>"
|
|
||||||
type UserId struct {
|
|
||||||
Id string // By convention, this takes the form "Full Name (Comment) <email@example.com>" which is split out in the fields below.
|
|
||||||
|
|
||||||
Name, Comment, Email string
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasInvalidCharacters(s string) bool {
|
|
||||||
for _, c := range s {
|
|
||||||
switch c {
|
|
||||||
case '(', ')', '<', '>', 0:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUserId returns a UserId or nil if any of the arguments contain invalid
|
|
||||||
// characters. The invalid characters are '\x00', '(', ')', '<' and '>'
|
|
||||||
func NewUserId(name, comment, email string) *UserId {
|
|
||||||
// RFC 4880 doesn't deal with the structure of userid strings; the
|
|
||||||
// name, comment and email form is just a convention. However, there's
|
|
||||||
// no convention about escaping the metacharacters and GPG just refuses
|
|
||||||
// to create user ids where, say, the name contains a '('. We mirror
|
|
||||||
// this behaviour.
|
|
||||||
|
|
||||||
if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := new(UserId)
|
|
||||||
uid.Name, uid.Comment, uid.Email = name, comment, email
|
|
||||||
uid.Id = name
|
|
||||||
if len(comment) > 0 {
|
|
||||||
if len(uid.Id) > 0 {
|
|
||||||
uid.Id += " "
|
|
||||||
}
|
|
||||||
uid.Id += "("
|
|
||||||
uid.Id += comment
|
|
||||||
uid.Id += ")"
|
|
||||||
}
|
|
||||||
if len(email) > 0 {
|
|
||||||
if len(uid.Id) > 0 {
|
|
||||||
uid.Id += " "
|
|
||||||
}
|
|
||||||
uid.Id += "<"
|
|
||||||
uid.Id += email
|
|
||||||
uid.Id += ">"
|
|
||||||
}
|
|
||||||
return uid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uid *UserId) parse(r io.Reader) (err error) {
|
|
||||||
// RFC 4880, section 5.11
|
|
||||||
b, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uid.Id = string(b)
|
|
||||||
uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize marshals uid to w in the form of an OpenPGP packet, including
|
|
||||||
// header.
|
|
||||||
func (uid *UserId) Serialize(w io.Writer) error {
|
|
||||||
err := serializeHeader(w, packetTypeUserId, len(uid.Id))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write([]byte(uid.Id))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseUserId extracts the name, comment and email from a user id string that
|
|
||||||
// is formatted as "Full Name (Comment) <email@example.com>".
|
|
||||||
func parseUserId(id string) (name, comment, email string) {
|
|
||||||
var n, c, e struct {
|
|
||||||
start, end int
|
|
||||||
}
|
|
||||||
var state int
|
|
||||||
|
|
||||||
for offset, rune := range id {
|
|
||||||
switch state {
|
|
||||||
case 0:
|
|
||||||
// Entering name
|
|
||||||
n.start = offset
|
|
||||||
state = 1
|
|
||||||
fallthrough
|
|
||||||
case 1:
|
|
||||||
// In name
|
|
||||||
if rune == '(' {
|
|
||||||
state = 2
|
|
||||||
n.end = offset
|
|
||||||
} else if rune == '<' {
|
|
||||||
state = 5
|
|
||||||
n.end = offset
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
// Entering comment
|
|
||||||
c.start = offset
|
|
||||||
state = 3
|
|
||||||
fallthrough
|
|
||||||
case 3:
|
|
||||||
// In comment
|
|
||||||
if rune == ')' {
|
|
||||||
state = 4
|
|
||||||
c.end = offset
|
|
||||||
}
|
|
||||||
case 4:
|
|
||||||
// Between comment and email
|
|
||||||
if rune == '<' {
|
|
||||||
state = 5
|
|
||||||
}
|
|
||||||
case 5:
|
|
||||||
// Entering email
|
|
||||||
e.start = offset
|
|
||||||
state = 6
|
|
||||||
fallthrough
|
|
||||||
case 6:
|
|
||||||
// In email
|
|
||||||
if rune == '>' {
|
|
||||||
state = 7
|
|
||||||
e.end = offset
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// After email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch state {
|
|
||||||
case 1:
|
|
||||||
// ended in the name
|
|
||||||
n.end = len(id)
|
|
||||||
case 3:
|
|
||||||
// ended in comment
|
|
||||||
c.end = len(id)
|
|
||||||
case 6:
|
|
||||||
// ended in email
|
|
||||||
e.end = len(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
name = strings.TrimSpace(id[n.start:n.end])
|
|
||||||
comment = strings.TrimSpace(id[c.start:c.end])
|
|
||||||
email = strings.TrimSpace(id[e.start:e.end])
|
|
||||||
|
|
||||||
// RFC 2822 3.4: alternate simple form of a mailbox
|
|
||||||
if email == "" && strings.ContainsRune(name, '@') {
|
|
||||||
email = name
|
|
||||||
name = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,592 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package openpgp implements high level operations on OpenPGP messages.
|
|
||||||
package openpgp // import "github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
_ "crypto/sha256"
|
|
||||||
_ "crypto/sha512"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
_ "golang.org/x/crypto/sha3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SignatureType is the armor type for a PGP signature.
|
|
||||||
var SignatureType = "PGP SIGNATURE"
|
|
||||||
|
|
||||||
// readArmored reads an armored block with the given type.
|
|
||||||
func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) {
|
|
||||||
block, err := armor.Decode(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if block.Type != expectedType {
|
|
||||||
return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return block.Body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MessageDetails contains the result of parsing an OpenPGP encrypted and/or
|
|
||||||
// signed message.
|
|
||||||
type MessageDetails struct {
|
|
||||||
IsEncrypted bool // true if the message was encrypted.
|
|
||||||
EncryptedToKeyIds []uint64 // the list of recipient key ids.
|
|
||||||
IsSymmetricallyEncrypted bool // true if a passphrase could have decrypted the message.
|
|
||||||
DecryptedWith Key // the private key used to decrypt the message, if any.
|
|
||||||
IsSigned bool // true if the message is signed.
|
|
||||||
SignedByKeyId uint64 // the key id of the signer, if any.
|
|
||||||
SignedBy *Key // the key of the signer, if available.
|
|
||||||
LiteralData *packet.LiteralData // the metadata of the contents
|
|
||||||
UnverifiedBody io.Reader // the contents of the message.
|
|
||||||
|
|
||||||
// If IsSigned is true and SignedBy is non-zero then the signature will
|
|
||||||
// be verified as UnverifiedBody is read. The signature cannot be
|
|
||||||
// checked until the whole of UnverifiedBody is read so UnverifiedBody
|
|
||||||
// must be consumed until EOF before the data can be trusted. Even if a
|
|
||||||
// message isn't signed (or the signer is unknown) the data may contain
|
|
||||||
// an authentication code that is only checked once UnverifiedBody has
|
|
||||||
// been consumed. Once EOF has been seen, the following fields are
|
|
||||||
// valid. (An authentication code failure is reported as a
|
|
||||||
// SignatureError error when reading from UnverifiedBody.)
|
|
||||||
Signature *packet.Signature // the signature packet itself.
|
|
||||||
SignatureError error // nil if the signature is good.
|
|
||||||
UnverifiedSignatures []*packet.Signature // all other unverified signature packets.
|
|
||||||
|
|
||||||
decrypted io.ReadCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
// A PromptFunction is used as a callback by functions that may need to decrypt
|
|
||||||
// a private key, or prompt for a passphrase. It is called with a list of
|
|
||||||
// acceptable, encrypted private keys and a boolean that indicates whether a
|
|
||||||
// passphrase is usable. It should either decrypt a private key or return a
|
|
||||||
// passphrase to try. If the decrypted private key or given passphrase isn't
|
|
||||||
// correct, the function will be called again, forever. Any error returned will
|
|
||||||
// be passed up.
|
|
||||||
type PromptFunction func(keys []Key, symmetric bool) ([]byte, error)
|
|
||||||
|
|
||||||
// A keyEnvelopePair is used to store a private key with the envelope that
|
|
||||||
// contains a symmetric key, encrypted with that key.
|
|
||||||
type keyEnvelopePair struct {
|
|
||||||
key Key
|
|
||||||
encryptedKey *packet.EncryptedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMessage parses an OpenPGP message that may be signed and/or encrypted.
|
|
||||||
// The given KeyRing should contain both public keys (for signature
|
|
||||||
// verification) and, possibly encrypted, private keys for decrypting.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction, config *packet.Config) (md *MessageDetails, err error) {
|
|
||||||
var p packet.Packet
|
|
||||||
|
|
||||||
var symKeys []*packet.SymmetricKeyEncrypted
|
|
||||||
var pubKeys []keyEnvelopePair
|
|
||||||
// Integrity protected encrypted packet: SymmetricallyEncrypted or AEADEncrypted
|
|
||||||
var edp packet.EncryptedDataPacket
|
|
||||||
|
|
||||||
packets := packet.NewReader(r)
|
|
||||||
md = new(MessageDetails)
|
|
||||||
md.IsEncrypted = true
|
|
||||||
|
|
||||||
// The message, if encrypted, starts with a number of packets
|
|
||||||
// containing an encrypted decryption key. The decryption key is either
|
|
||||||
// encrypted to a public key, or with a passphrase. This loop
|
|
||||||
// collects these packets.
|
|
||||||
ParsePackets:
|
|
||||||
for {
|
|
||||||
p, err = packets.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch p := p.(type) {
|
|
||||||
case *packet.SymmetricKeyEncrypted:
|
|
||||||
// This packet contains the decryption key encrypted with a passphrase.
|
|
||||||
md.IsSymmetricallyEncrypted = true
|
|
||||||
symKeys = append(symKeys, p)
|
|
||||||
case *packet.EncryptedKey:
|
|
||||||
// This packet contains the decryption key encrypted to a public key.
|
|
||||||
md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId)
|
|
||||||
switch p.Algo {
|
|
||||||
case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal, packet.PubKeyAlgoECDH:
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if keyring != nil {
|
|
||||||
var keys []Key
|
|
||||||
if p.KeyId == 0 {
|
|
||||||
keys = keyring.DecryptionKeys()
|
|
||||||
} else {
|
|
||||||
keys = keyring.KeysById(p.KeyId)
|
|
||||||
}
|
|
||||||
for _, k := range keys {
|
|
||||||
pubKeys = append(pubKeys, keyEnvelopePair{k, p})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *packet.SymmetricallyEncrypted:
|
|
||||||
if !p.IntegrityProtected && !config.AllowUnauthenticatedMessages() {
|
|
||||||
return nil, errors.UnsupportedError("message is not integrity protected")
|
|
||||||
}
|
|
||||||
edp = p
|
|
||||||
break ParsePackets
|
|
||||||
case *packet.AEADEncrypted:
|
|
||||||
edp = p
|
|
||||||
break ParsePackets
|
|
||||||
case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature:
|
|
||||||
// This message isn't encrypted.
|
|
||||||
if len(symKeys) != 0 || len(pubKeys) != 0 {
|
|
||||||
return nil, errors.StructuralError("key material not followed by encrypted message")
|
|
||||||
}
|
|
||||||
packets.Unread(p)
|
|
||||||
return readSignedMessage(packets, nil, keyring, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var candidates []Key
|
|
||||||
var decrypted io.ReadCloser
|
|
||||||
|
|
||||||
// Now that we have the list of encrypted keys we need to decrypt at
|
|
||||||
// least one of them or, if we cannot, we need to call the prompt
|
|
||||||
// function so that it can decrypt a key or give us a passphrase.
|
|
||||||
FindKey:
|
|
||||||
for {
|
|
||||||
// See if any of the keys already have a private key available
|
|
||||||
candidates = candidates[:0]
|
|
||||||
candidateFingerprints := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, pk := range pubKeys {
|
|
||||||
if pk.key.PrivateKey == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !pk.key.PrivateKey.Encrypted {
|
|
||||||
if len(pk.encryptedKey.Key) == 0 {
|
|
||||||
errDec := pk.encryptedKey.Decrypt(pk.key.PrivateKey, config)
|
|
||||||
if errDec != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Try to decrypt symmetrically encrypted
|
|
||||||
decrypted, err = edp.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key)
|
|
||||||
if err != nil && err != errors.ErrKeyIncorrect {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if decrypted != nil {
|
|
||||||
md.DecryptedWith = pk.key
|
|
||||||
break FindKey
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fpr := string(pk.key.PublicKey.Fingerprint[:])
|
|
||||||
if v := candidateFingerprints[fpr]; v {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
candidates = append(candidates, pk.key)
|
|
||||||
candidateFingerprints[fpr] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(candidates) == 0 && len(symKeys) == 0 {
|
|
||||||
return nil, errors.ErrKeyIncorrect
|
|
||||||
}
|
|
||||||
|
|
||||||
if prompt == nil {
|
|
||||||
return nil, errors.ErrKeyIncorrect
|
|
||||||
}
|
|
||||||
|
|
||||||
passphrase, err := prompt(candidates, len(symKeys) != 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try the symmetric passphrase first
|
|
||||||
if len(symKeys) != 0 && passphrase != nil {
|
|
||||||
for _, s := range symKeys {
|
|
||||||
key, cipherFunc, err := s.Decrypt(passphrase)
|
|
||||||
// In v4, on wrong passphrase, session key decryption is very likely to result in an invalid cipherFunc:
|
|
||||||
// only for < 5% of cases we will proceed to decrypt the data
|
|
||||||
if err == nil {
|
|
||||||
decrypted, err = edp.Decrypt(cipherFunc, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if decrypted != nil {
|
|
||||||
break FindKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
md.decrypted = decrypted
|
|
||||||
if err := packets.Push(decrypted); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mdFinal, sensitiveParsingErr := readSignedMessage(packets, md, keyring, config)
|
|
||||||
if sensitiveParsingErr != nil {
|
|
||||||
return nil, errors.StructuralError("parsing error")
|
|
||||||
}
|
|
||||||
return mdFinal, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readSignedMessage reads a possibly signed message if mdin is non-zero then
|
|
||||||
// that structure is updated and returned. Otherwise a fresh MessageDetails is
|
|
||||||
// used.
|
|
||||||
func readSignedMessage(packets *packet.Reader, mdin *MessageDetails, keyring KeyRing, config *packet.Config) (md *MessageDetails, err error) {
|
|
||||||
if mdin == nil {
|
|
||||||
mdin = new(MessageDetails)
|
|
||||||
}
|
|
||||||
md = mdin
|
|
||||||
|
|
||||||
var p packet.Packet
|
|
||||||
var h hash.Hash
|
|
||||||
var wrappedHash hash.Hash
|
|
||||||
var prevLast bool
|
|
||||||
FindLiteralData:
|
|
||||||
for {
|
|
||||||
p, err = packets.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch p := p.(type) {
|
|
||||||
case *packet.Compressed:
|
|
||||||
if err := packets.Push(p.Body); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *packet.OnePassSignature:
|
|
||||||
if prevLast {
|
|
||||||
return nil, errors.UnsupportedError("nested signature packets")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.IsLast {
|
|
||||||
prevLast = true
|
|
||||||
}
|
|
||||||
|
|
||||||
h, wrappedHash, err = hashForSignature(p.Hash, p.SigType)
|
|
||||||
if err != nil {
|
|
||||||
md.SignatureError = err
|
|
||||||
}
|
|
||||||
|
|
||||||
md.IsSigned = true
|
|
||||||
md.SignedByKeyId = p.KeyId
|
|
||||||
if keyring != nil {
|
|
||||||
keys := keyring.KeysByIdUsage(p.KeyId, packet.KeyFlagSign)
|
|
||||||
if len(keys) > 0 {
|
|
||||||
md.SignedBy = &keys[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *packet.LiteralData:
|
|
||||||
md.LiteralData = p
|
|
||||||
break FindLiteralData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if md.IsSigned && md.SignatureError == nil {
|
|
||||||
md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md, config}
|
|
||||||
} else if md.decrypted != nil {
|
|
||||||
md.UnverifiedBody = checkReader{md}
|
|
||||||
} else {
|
|
||||||
md.UnverifiedBody = md.LiteralData.Body
|
|
||||||
}
|
|
||||||
|
|
||||||
return md, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashForSignature returns a pair of hashes that can be used to verify a
|
|
||||||
// signature. The signature may specify that the contents of the signed message
|
|
||||||
// should be preprocessed (i.e. to normalize line endings). Thus this function
|
|
||||||
// returns two hashes. The second should be used to hash the message itself and
|
|
||||||
// performs any needed preprocessing.
|
|
||||||
func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
|
|
||||||
if _, ok := algorithm.HashToHashIdWithSha1(hashFunc); !ok {
|
|
||||||
return nil, nil, errors.UnsupportedError("unsupported hash function")
|
|
||||||
}
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashFunc)))
|
|
||||||
}
|
|
||||||
h := hashFunc.New()
|
|
||||||
|
|
||||||
switch sigType {
|
|
||||||
case packet.SigTypeBinary:
|
|
||||||
return h, h, nil
|
|
||||||
case packet.SigTypeText:
|
|
||||||
return h, NewCanonicalTextHash(h), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF
|
|
||||||
// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger
|
|
||||||
// MDC checks.
|
|
||||||
type checkReader struct {
|
|
||||||
md *MessageDetails
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr checkReader) Read(buf []byte) (int, error) {
|
|
||||||
n, sensitiveParsingError := cr.md.LiteralData.Body.Read(buf)
|
|
||||||
if sensitiveParsingError == io.EOF {
|
|
||||||
mdcErr := cr.md.decrypted.Close()
|
|
||||||
if mdcErr != nil {
|
|
||||||
return n, mdcErr
|
|
||||||
}
|
|
||||||
return n, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if sensitiveParsingError != nil {
|
|
||||||
return n, errors.StructuralError("parsing error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes
|
|
||||||
// the data as it is read. When it sees an EOF from the underlying io.Reader
|
|
||||||
// it parses and checks a trailing Signature packet and triggers any MDC checks.
|
|
||||||
type signatureCheckReader struct {
|
|
||||||
packets *packet.Reader
|
|
||||||
h, wrappedHash hash.Hash
|
|
||||||
md *MessageDetails
|
|
||||||
config *packet.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (scr *signatureCheckReader) Read(buf []byte) (int, error) {
|
|
||||||
n, sensitiveParsingError := scr.md.LiteralData.Body.Read(buf)
|
|
||||||
|
|
||||||
// Hash only if required
|
|
||||||
if scr.md.SignedBy != nil {
|
|
||||||
scr.wrappedHash.Write(buf[:n])
|
|
||||||
}
|
|
||||||
|
|
||||||
if sensitiveParsingError == io.EOF {
|
|
||||||
var p packet.Packet
|
|
||||||
var readError error
|
|
||||||
var sig *packet.Signature
|
|
||||||
|
|
||||||
p, readError = scr.packets.Next()
|
|
||||||
for readError == nil {
|
|
||||||
var ok bool
|
|
||||||
if sig, ok = p.(*packet.Signature); ok {
|
|
||||||
if sig.Version == 5 && (sig.SigType == 0x00 || sig.SigType == 0x01) {
|
|
||||||
sig.Metadata = scr.md.LiteralData
|
|
||||||
}
|
|
||||||
|
|
||||||
// If signature KeyID matches
|
|
||||||
if scr.md.SignedBy != nil && *sig.IssuerKeyId == scr.md.SignedByKeyId {
|
|
||||||
key := scr.md.SignedBy
|
|
||||||
signatureError := key.PublicKey.VerifySignature(scr.h, sig)
|
|
||||||
if signatureError == nil {
|
|
||||||
signatureError = checkSignatureDetails(key, sig, scr.config)
|
|
||||||
}
|
|
||||||
scr.md.Signature = sig
|
|
||||||
scr.md.SignatureError = signatureError
|
|
||||||
} else {
|
|
||||||
scr.md.UnverifiedSignatures = append(scr.md.UnverifiedSignatures, sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p, readError = scr.packets.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
if scr.md.SignedBy != nil && scr.md.Signature == nil {
|
|
||||||
if scr.md.UnverifiedSignatures == nil {
|
|
||||||
scr.md.SignatureError = errors.StructuralError("LiteralData not followed by signature")
|
|
||||||
} else {
|
|
||||||
scr.md.SignatureError = errors.StructuralError("No matching signature found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SymmetricallyEncrypted packet, if any, might have an
|
|
||||||
// unsigned hash of its own. In order to check this we need to
|
|
||||||
// close that Reader.
|
|
||||||
if scr.md.decrypted != nil {
|
|
||||||
mdcErr := scr.md.decrypted.Close()
|
|
||||||
if mdcErr != nil {
|
|
||||||
return n, mdcErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if sensitiveParsingError != nil {
|
|
||||||
return n, errors.StructuralError("parsing error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetachedSignature takes a signed file and a detached signature and
|
|
||||||
// returns the signature packet and the entity the signature was signed by,
|
|
||||||
// if any, and a possible signature verification error.
|
|
||||||
// If the signer isn't known, ErrUnknownIssuer is returned.
|
|
||||||
func VerifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) {
|
|
||||||
var expectedHashes []crypto.Hash
|
|
||||||
return verifyDetachedSignature(keyring, signed, signature, expectedHashes, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetachedSignatureAndHash performs the same actions as
|
|
||||||
// VerifyDetachedSignature and checks that the expected hash functions were used.
|
|
||||||
func VerifyDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) {
|
|
||||||
return verifyDetachedSignature(keyring, signed, signature, expectedHashes, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDetachedSignature takes a signed file and a detached signature and
|
|
||||||
// returns the entity the signature was signed by, if any, and a possible
|
|
||||||
// signature verification error. If the signer isn't known,
|
|
||||||
// ErrUnknownIssuer is returned.
|
|
||||||
func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) {
|
|
||||||
var expectedHashes []crypto.Hash
|
|
||||||
return CheckDetachedSignatureAndHash(keyring, signed, signature, expectedHashes, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDetachedSignatureAndHash performs the same actions as
|
|
||||||
// CheckDetachedSignature and checks that the expected hash functions were used.
|
|
||||||
func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (signer *Entity, err error) {
|
|
||||||
_, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, config)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) {
|
|
||||||
var issuerKeyId uint64
|
|
||||||
var hashFunc crypto.Hash
|
|
||||||
var sigType packet.SignatureType
|
|
||||||
var keys []Key
|
|
||||||
var p packet.Packet
|
|
||||||
|
|
||||||
expectedHashesLen := len(expectedHashes)
|
|
||||||
packets := packet.NewReader(signature)
|
|
||||||
for {
|
|
||||||
p, err = packets.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil, nil, errors.ErrUnknownIssuer
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
sig, ok = p.(*packet.Signature)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.StructuralError("non signature packet found")
|
|
||||||
}
|
|
||||||
if sig.IssuerKeyId == nil {
|
|
||||||
return nil, nil, errors.StructuralError("signature doesn't have an issuer")
|
|
||||||
}
|
|
||||||
issuerKeyId = *sig.IssuerKeyId
|
|
||||||
hashFunc = sig.Hash
|
|
||||||
sigType = sig.SigType
|
|
||||||
|
|
||||||
for i, expectedHash := range expectedHashes {
|
|
||||||
if hashFunc == expectedHash {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i+1 == expectedHashesLen {
|
|
||||||
return nil, nil, errors.StructuralError("hash algorithm mismatch with cleartext message headers")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keys = keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign)
|
|
||||||
if len(keys) > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keys) == 0 {
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
h, wrappedHash, err := hashForSignature(hashFunc, sigType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, key := range keys {
|
|
||||||
err = key.PublicKey.VerifySignature(h, sig)
|
|
||||||
if err == nil {
|
|
||||||
return sig, key.Entity, checkSignatureDetails(&key, sig, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckArmoredDetachedSignature performs the same actions as
|
|
||||||
// CheckDetachedSignature but expects the signature to be armored.
|
|
||||||
func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) {
|
|
||||||
body, err := readArmored(signature, SignatureType)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return CheckDetachedSignature(keyring, signed, body, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkSignatureDetails returns an error if:
|
|
||||||
// - The signature (or one of the binding signatures mentioned below)
|
|
||||||
// has a unknown critical notation data subpacket
|
|
||||||
// - The primary key of the signing entity is revoked
|
|
||||||
// - The primary identity is revoked
|
|
||||||
// - The signature is expired
|
|
||||||
// - The primary key of the signing entity is expired according to the
|
|
||||||
// primary identity binding signature
|
|
||||||
//
|
|
||||||
// ... or, if the signature was signed by a subkey and:
|
|
||||||
// - The signing subkey is revoked
|
|
||||||
// - The signing subkey is expired according to the subkey binding signature
|
|
||||||
// - The signing subkey binding signature is expired
|
|
||||||
// - The signing subkey cross-signature is expired
|
|
||||||
//
|
|
||||||
// NOTE: The order of these checks is important, as the caller may choose to
|
|
||||||
// ignore ErrSignatureExpired or ErrKeyExpired errors, but should never
|
|
||||||
// ignore any other errors.
|
|
||||||
//
|
|
||||||
// TODO: Also return an error if:
|
|
||||||
// - The primary key is expired according to a direct-key signature
|
|
||||||
// - (For V5 keys only:) The direct-key signature (exists and) is expired
|
|
||||||
func checkSignatureDetails(key *Key, signature *packet.Signature, config *packet.Config) error {
|
|
||||||
now := config.Now()
|
|
||||||
primaryIdentity := key.Entity.PrimaryIdentity()
|
|
||||||
signedBySubKey := key.PublicKey != key.Entity.PrimaryKey
|
|
||||||
sigsToCheck := []*packet.Signature{signature, primaryIdentity.SelfSignature}
|
|
||||||
if signedBySubKey {
|
|
||||||
sigsToCheck = append(sigsToCheck, key.SelfSignature, key.SelfSignature.EmbeddedSignature)
|
|
||||||
}
|
|
||||||
for _, sig := range sigsToCheck {
|
|
||||||
for _, notation := range sig.Notations {
|
|
||||||
if notation.IsCritical && !config.KnownNotation(notation.Name) {
|
|
||||||
return errors.SignatureError("unknown critical notation: " + notation.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if key.Entity.Revoked(now) || // primary key is revoked
|
|
||||||
(signedBySubKey && key.Revoked(now)) || // subkey is revoked
|
|
||||||
primaryIdentity.Revoked(now) { // primary identity is revoked
|
|
||||||
return errors.ErrKeyRevoked
|
|
||||||
}
|
|
||||||
if key.Entity.PrimaryKey.KeyExpired(primaryIdentity.SelfSignature, now) { // primary key is expired
|
|
||||||
return errors.ErrKeyExpired
|
|
||||||
}
|
|
||||||
if signedBySubKey {
|
|
||||||
if key.PublicKey.KeyExpired(key.SelfSignature, now) { // subkey is expired
|
|
||||||
return errors.ErrKeyExpired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, sig := range sigsToCheck {
|
|
||||||
if sig.SigExpired(now) { // any of the relevant signatures are expired
|
|
||||||
return errors.ErrSignatureExpired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@ -1,407 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package s2k implements the various OpenPGP string-to-key transforms as
|
|
||||||
// specified in RFC 4800 section 3.7.1, and Argon2 specified in
|
|
||||||
// draft-ietf-openpgp-crypto-refresh-08 section 3.7.1.4.
|
|
||||||
package s2k // import "github.com/ProtonMail/go-crypto/openpgp/s2k"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
"golang.org/x/crypto/argon2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Mode uint8
|
|
||||||
|
|
||||||
// Defines the default S2KMode constants
|
|
||||||
//
|
|
||||||
// 0 (simple), 1(salted), 3(iterated), 4(argon2)
|
|
||||||
const (
|
|
||||||
SimpleS2K Mode = 0
|
|
||||||
SaltedS2K Mode = 1
|
|
||||||
IteratedSaltedS2K Mode = 3
|
|
||||||
Argon2S2K Mode = 4
|
|
||||||
GnuS2K Mode = 101
|
|
||||||
)
|
|
||||||
|
|
||||||
const Argon2SaltSize int = 16
|
|
||||||
|
|
||||||
// Params contains all the parameters of the s2k packet
|
|
||||||
type Params struct {
|
|
||||||
// mode is the mode of s2k function.
|
|
||||||
// It can be 0 (simple), 1(salted), 3(iterated)
|
|
||||||
// 2(reserved) 100-110(private/experimental).
|
|
||||||
mode Mode
|
|
||||||
// hashId is the ID of the hash function used in any of the modes
|
|
||||||
hashId byte
|
|
||||||
// salt is a byte array to use as a salt in hashing process or argon2
|
|
||||||
saltBytes [Argon2SaltSize]byte
|
|
||||||
// countByte is used to determine how many rounds of hashing are to
|
|
||||||
// be performed in s2k mode 3. See RFC 4880 Section 3.7.1.3.
|
|
||||||
countByte byte
|
|
||||||
// passes is a parameter in Argon2 to determine the number of iterations
|
|
||||||
// See RFC the crypto refresh Section 3.7.1.4.
|
|
||||||
passes byte
|
|
||||||
// parallelism is a parameter in Argon2 to determine the degree of paralellism
|
|
||||||
// See RFC the crypto refresh Section 3.7.1.4.
|
|
||||||
parallelism byte
|
|
||||||
// memoryExp is a parameter in Argon2 to determine the memory usage
|
|
||||||
// i.e., 2 ** memoryExp kibibytes
|
|
||||||
// See RFC the crypto refresh Section 3.7.1.4.
|
|
||||||
memoryExp byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeCount converts an iterative "count" in the range 1024 to
|
|
||||||
// 65011712, inclusive, to an encoded count. The return value is the
|
|
||||||
// octet that is actually stored in the GPG file. encodeCount panics
|
|
||||||
// if i is not in the above range (encodedCount above takes care to
|
|
||||||
// pass i in the correct range). See RFC 4880 Section 3.7.7.1.
|
|
||||||
func encodeCount(i int) uint8 {
|
|
||||||
if i < 65536 || i > 65011712 {
|
|
||||||
panic("count arg i outside the required range")
|
|
||||||
}
|
|
||||||
|
|
||||||
for encoded := 96; encoded < 256; encoded++ {
|
|
||||||
count := decodeCount(uint8(encoded))
|
|
||||||
if count >= i {
|
|
||||||
return uint8(encoded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 255
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeCount returns the s2k mode 3 iterative "count" corresponding to
|
|
||||||
// the encoded octet c.
|
|
||||||
func decodeCount(c uint8) int {
|
|
||||||
return (16 + int(c&15)) << (uint32(c>>4) + 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeMemory converts the Argon2 "memory" in the range parallelism*8 to
|
|
||||||
// 2**31, inclusive, to an encoded memory. The return value is the
|
|
||||||
// octet that is actually stored in the GPG file. encodeMemory panics
|
|
||||||
// if is not in the above range
|
|
||||||
// See OpenPGP crypto refresh Section 3.7.1.4.
|
|
||||||
func encodeMemory(memory uint32, parallelism uint8) uint8 {
|
|
||||||
if memory < (8 * uint32(parallelism)) || memory > uint32(2147483648) {
|
|
||||||
panic("Memory argument memory is outside the required range")
|
|
||||||
}
|
|
||||||
|
|
||||||
for exp := 3; exp < 31; exp++ {
|
|
||||||
compare := decodeMemory(uint8(exp))
|
|
||||||
if compare >= memory {
|
|
||||||
return uint8(exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 31
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeMemory computes the decoded memory in kibibytes as 2**memoryExponent
|
|
||||||
func decodeMemory(memoryExponent uint8) uint32 {
|
|
||||||
return uint32(1) << memoryExponent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple writes to out the result of computing the Simple S2K function (RFC
|
|
||||||
// 4880, section 3.7.1.1) using the given hash and input passphrase.
|
|
||||||
func Simple(out []byte, h hash.Hash, in []byte) {
|
|
||||||
Salted(out, h, in, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
var zero [1]byte
|
|
||||||
|
|
||||||
// Salted writes to out the result of computing the Salted S2K function (RFC
|
|
||||||
// 4880, section 3.7.1.2) using the given hash, input passphrase and salt.
|
|
||||||
func Salted(out []byte, h hash.Hash, in []byte, salt []byte) {
|
|
||||||
done := 0
|
|
||||||
var digest []byte
|
|
||||||
|
|
||||||
for i := 0; done < len(out); i++ {
|
|
||||||
h.Reset()
|
|
||||||
for j := 0; j < i; j++ {
|
|
||||||
h.Write(zero[:])
|
|
||||||
}
|
|
||||||
h.Write(salt)
|
|
||||||
h.Write(in)
|
|
||||||
digest = h.Sum(digest[:0])
|
|
||||||
n := copy(out[done:], digest)
|
|
||||||
done += n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterated writes to out the result of computing the Iterated and Salted S2K
|
|
||||||
// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase,
|
|
||||||
// salt and iteration count.
|
|
||||||
func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
|
|
||||||
combined := make([]byte, len(in)+len(salt))
|
|
||||||
copy(combined, salt)
|
|
||||||
copy(combined[len(salt):], in)
|
|
||||||
|
|
||||||
if count < len(combined) {
|
|
||||||
count = len(combined)
|
|
||||||
}
|
|
||||||
|
|
||||||
done := 0
|
|
||||||
var digest []byte
|
|
||||||
for i := 0; done < len(out); i++ {
|
|
||||||
h.Reset()
|
|
||||||
for j := 0; j < i; j++ {
|
|
||||||
h.Write(zero[:])
|
|
||||||
}
|
|
||||||
written := 0
|
|
||||||
for written < count {
|
|
||||||
if written+len(combined) > count {
|
|
||||||
todo := count - written
|
|
||||||
h.Write(combined[:todo])
|
|
||||||
written = count
|
|
||||||
} else {
|
|
||||||
h.Write(combined)
|
|
||||||
written += len(combined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
digest = h.Sum(digest[:0])
|
|
||||||
n := copy(out[done:], digest)
|
|
||||||
done += n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argon2 writes to out the key derived from the password (in) with the Argon2
|
|
||||||
// function (the crypto refresh, section 3.7.1.4)
|
|
||||||
func Argon2(out []byte, in []byte, salt []byte, passes uint8, paralellism uint8, memoryExp uint8) {
|
|
||||||
key := argon2.IDKey(in, salt, uint32(passes), decodeMemory(memoryExp), paralellism, uint32(len(out)))
|
|
||||||
copy(out[:], key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate generates valid parameters from given configuration.
|
|
||||||
// It will enforce the Iterated and Salted or Argon2 S2K method.
|
|
||||||
func Generate(rand io.Reader, c *Config) (*Params, error) {
|
|
||||||
var params *Params
|
|
||||||
if c != nil && c.Mode() == Argon2S2K {
|
|
||||||
// handle Argon2 case
|
|
||||||
argonConfig := c.Argon2()
|
|
||||||
params = &Params{
|
|
||||||
mode: Argon2S2K,
|
|
||||||
passes: argonConfig.Passes(),
|
|
||||||
parallelism: argonConfig.Parallelism(),
|
|
||||||
memoryExp: argonConfig.EncodedMemory(),
|
|
||||||
}
|
|
||||||
} else if c != nil && c.PassphraseIsHighEntropy && c.Mode() == SaltedS2K { // Allow SaltedS2K if PassphraseIsHighEntropy
|
|
||||||
hashId, ok := algorithm.HashToHashId(c.hash())
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.UnsupportedError("no such hash")
|
|
||||||
}
|
|
||||||
|
|
||||||
params = &Params{
|
|
||||||
mode: SaltedS2K,
|
|
||||||
hashId: hashId,
|
|
||||||
}
|
|
||||||
} else { // Enforce IteratedSaltedS2K method otherwise
|
|
||||||
hashId, ok := algorithm.HashToHashId(c.hash())
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.UnsupportedError("no such hash")
|
|
||||||
}
|
|
||||||
if c != nil {
|
|
||||||
c.S2KMode = IteratedSaltedS2K
|
|
||||||
}
|
|
||||||
params = &Params{
|
|
||||||
mode: IteratedSaltedS2K,
|
|
||||||
hashId: hashId,
|
|
||||||
countByte: c.EncodedCount(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := io.ReadFull(rand, params.salt()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return params, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads a binary specification for a string-to-key transformation from r
|
|
||||||
// and returns a function which performs that transform. If the S2K is a special
|
|
||||||
// GNU extension that indicates that the private key is missing, then the error
|
|
||||||
// returned is errors.ErrDummyPrivateKey.
|
|
||||||
func Parse(r io.Reader) (f func(out, in []byte), err error) {
|
|
||||||
params, err := ParseIntoParams(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return params.Function()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseIntoParams reads a binary specification for a string-to-key
|
|
||||||
// transformation from r and returns a struct describing the s2k parameters.
|
|
||||||
func ParseIntoParams(r io.Reader) (params *Params, err error) {
|
|
||||||
var buf [Argon2SaltSize + 3]byte
|
|
||||||
|
|
||||||
_, err = io.ReadFull(r, buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params = &Params{
|
|
||||||
mode: Mode(buf[0]),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch params.mode {
|
|
||||||
case SimpleS2K:
|
|
||||||
_, err = io.ReadFull(r, buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params.hashId = buf[0]
|
|
||||||
return params, nil
|
|
||||||
case SaltedS2K:
|
|
||||||
_, err = io.ReadFull(r, buf[:9])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params.hashId = buf[0]
|
|
||||||
copy(params.salt(), buf[1:9])
|
|
||||||
return params, nil
|
|
||||||
case IteratedSaltedS2K:
|
|
||||||
_, err = io.ReadFull(r, buf[:10])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params.hashId = buf[0]
|
|
||||||
copy(params.salt(), buf[1:9])
|
|
||||||
params.countByte = buf[9]
|
|
||||||
return params, nil
|
|
||||||
case Argon2S2K:
|
|
||||||
_, err = io.ReadFull(r, buf[:Argon2SaltSize+3])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
copy(params.salt(), buf[:Argon2SaltSize])
|
|
||||||
params.passes = buf[Argon2SaltSize]
|
|
||||||
params.parallelism = buf[Argon2SaltSize+1]
|
|
||||||
params.memoryExp = buf[Argon2SaltSize+2]
|
|
||||||
return params, nil
|
|
||||||
case GnuS2K:
|
|
||||||
// This is a GNU extension. See
|
|
||||||
// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=fe55ae16ab4e26d8356dc574c9e8bc935e71aef1;hb=23191d7851eae2217ecdac6484349849a24fd94a#l1109
|
|
||||||
if _, err = io.ReadFull(r, buf[:5]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params.hashId = buf[0]
|
|
||||||
if buf[1] == 'G' && buf[2] == 'N' && buf[3] == 'U' && buf[4] == 1 {
|
|
||||||
return params, nil
|
|
||||||
}
|
|
||||||
return nil, errors.UnsupportedError("GNU S2K extension")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.UnsupportedError("S2K function")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (params *Params) Dummy() bool {
|
|
||||||
return params != nil && params.mode == GnuS2K
|
|
||||||
}
|
|
||||||
|
|
||||||
func (params *Params) salt() []byte {
|
|
||||||
switch params.mode {
|
|
||||||
case SaltedS2K, IteratedSaltedS2K: return params.saltBytes[:8]
|
|
||||||
case Argon2S2K: return params.saltBytes[:Argon2SaltSize]
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (params *Params) Function() (f func(out, in []byte), err error) {
|
|
||||||
if params.Dummy() {
|
|
||||||
return nil, errors.ErrDummyPrivateKey("dummy key found")
|
|
||||||
}
|
|
||||||
var hashObj crypto.Hash
|
|
||||||
if params.mode != Argon2S2K {
|
|
||||||
var ok bool
|
|
||||||
hashObj, ok = algorithm.HashIdToHashWithSha1(params.hashId)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(params.hashId)))
|
|
||||||
}
|
|
||||||
if !hashObj.Available() {
|
|
||||||
return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashObj)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch params.mode {
|
|
||||||
case SimpleS2K:
|
|
||||||
f := func(out, in []byte) {
|
|
||||||
Simple(out, hashObj.New(), in)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
case SaltedS2K:
|
|
||||||
f := func(out, in []byte) {
|
|
||||||
Salted(out, hashObj.New(), in, params.salt())
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
case IteratedSaltedS2K:
|
|
||||||
f := func(out, in []byte) {
|
|
||||||
Iterated(out, hashObj.New(), in, params.salt(), decodeCount(params.countByte))
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
case Argon2S2K:
|
|
||||||
f := func(out, in []byte) {
|
|
||||||
Argon2(out, in, params.salt(), params.passes, params.parallelism, params.memoryExp)
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.UnsupportedError("S2K function")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (params *Params) Serialize(w io.Writer) (err error) {
|
|
||||||
if _, err = w.Write([]byte{uint8(params.mode)}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.mode != Argon2S2K {
|
|
||||||
if _, err = w.Write([]byte{params.hashId}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if params.Dummy() {
|
|
||||||
_, err = w.Write(append([]byte("GNU"), 1))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.mode > 0 {
|
|
||||||
if _, err = w.Write(params.salt()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.mode == IteratedSaltedS2K {
|
|
||||||
_, err = w.Write([]byte{params.countByte})
|
|
||||||
}
|
|
||||||
if params.mode == Argon2S2K {
|
|
||||||
_, err = w.Write([]byte{params.passes, params.parallelism, params.memoryExp})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize salts and stretches the given passphrase and writes the
|
|
||||||
// resulting key into key. It also serializes an S2K descriptor to
|
|
||||||
// w. The key stretching can be configured with c, which may be
|
|
||||||
// nil. In that case, sensible defaults will be used.
|
|
||||||
func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Config) error {
|
|
||||||
params, err := Generate(rand, c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = params.Serialize(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := params.Function()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f(key, passphrase)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package s2k
|
|
||||||
|
|
||||||
// Cache stores keys derived with s2k functions from one passphrase
|
|
||||||
// to avoid recomputation if multiple items are encrypted with
|
|
||||||
// the same parameters.
|
|
||||||
type Cache map[Params][]byte
|
|
||||||
|
|
||||||
// GetOrComputeDerivedKey tries to retrieve the key
|
|
||||||
// for the given s2k parameters from the cache.
|
|
||||||
// If there is no hit, it derives the key with the s2k function from the passphrase,
|
|
||||||
// updates the cache, and returns the key.
|
|
||||||
func (c *Cache) GetOrComputeDerivedKey(passphrase []byte, params *Params, expectedKeySize int) ([]byte, error) {
|
|
||||||
key, found := (*c)[*params]
|
|
||||||
if !found || len(key) != expectedKeySize {
|
|
||||||
var err error
|
|
||||||
derivedKey := make([]byte, expectedKeySize)
|
|
||||||
s2k, err := params.Function()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s2k(derivedKey, passphrase)
|
|
||||||
(*c)[*params] = key
|
|
||||||
return derivedKey, nil
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
package s2k
|
|
||||||
|
|
||||||
import "crypto"
|
|
||||||
|
|
||||||
// Config collects configuration parameters for s2k key-stretching
|
|
||||||
// transformations. A nil *Config is valid and results in all default
|
|
||||||
// values.
|
|
||||||
type Config struct {
|
|
||||||
// S2K (String to Key) mode, used for key derivation in the context of secret key encryption
|
|
||||||
// and passphrase-encrypted data. Either s2k.Argon2S2K or s2k.IteratedSaltedS2K may be used.
|
|
||||||
// If the passphrase is a high-entropy key, indicated by setting PassphraseIsHighEntropy to true,
|
|
||||||
// s2k.SaltedS2K can also be used.
|
|
||||||
// Note: Argon2 is the strongest option but not all OpenPGP implementations are compatible with it
|
|
||||||
//(pending standardisation).
|
|
||||||
// 0 (simple), 1(salted), 3(iterated), 4(argon2)
|
|
||||||
// 2(reserved) 100-110(private/experimental).
|
|
||||||
S2KMode Mode
|
|
||||||
// Only relevant if S2KMode is not set to s2k.Argon2S2K.
|
|
||||||
// Hash is the default hash function to be used. If
|
|
||||||
// nil, SHA256 is used.
|
|
||||||
Hash crypto.Hash
|
|
||||||
// Argon2 parameters for S2K (String to Key).
|
|
||||||
// Only relevant if S2KMode is set to s2k.Argon2S2K.
|
|
||||||
// If nil, default parameters are used.
|
|
||||||
// For more details on the choice of parameters, see https://tools.ietf.org/html/rfc9106#section-4.
|
|
||||||
Argon2Config *Argon2Config
|
|
||||||
// Only relevant if S2KMode is set to s2k.IteratedSaltedS2K.
|
|
||||||
// Iteration count for Iterated S2K (String to Key). It
|
|
||||||
// determines the strength of the passphrase stretching when
|
|
||||||
// the said passphrase is hashed to produce a key. S2KCount
|
|
||||||
// should be between 65536 and 65011712, inclusive. If Config
|
|
||||||
// is nil or S2KCount is 0, the value 16777216 used. Not all
|
|
||||||
// values in the above range can be represented. S2KCount will
|
|
||||||
// be rounded up to the next representable value if it cannot
|
|
||||||
// be encoded exactly. When set, it is strongly encrouraged to
|
|
||||||
// use a value that is at least 65536. See RFC 4880 Section
|
|
||||||
// 3.7.1.3.
|
|
||||||
S2KCount int
|
|
||||||
// Indicates whether the passphrase passed by the application is a
|
|
||||||
// high-entropy key (e.g. it's randomly generated or derived from
|
|
||||||
// another passphrase using a strong key derivation function).
|
|
||||||
// When true, allows the S2KMode to be s2k.SaltedS2K.
|
|
||||||
// When the passphrase is not a high-entropy key, using SaltedS2K is
|
|
||||||
// insecure, and not allowed by draft-ietf-openpgp-crypto-refresh-08.
|
|
||||||
PassphraseIsHighEntropy bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argon2Config stores the Argon2 parameters
|
|
||||||
// A nil *Argon2Config is valid and results in all default
|
|
||||||
type Argon2Config struct {
|
|
||||||
NumberOfPasses uint8
|
|
||||||
DegreeOfParallelism uint8
|
|
||||||
// The memory parameter for Argon2 specifies desired memory usage in kibibytes.
|
|
||||||
// For example memory=64*1024 sets the memory cost to ~64 MB.
|
|
||||||
Memory uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Mode() Mode {
|
|
||||||
if c == nil {
|
|
||||||
return IteratedSaltedS2K
|
|
||||||
}
|
|
||||||
return c.S2KMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) hash() crypto.Hash {
|
|
||||||
if c == nil || uint(c.Hash) == 0 {
|
|
||||||
return crypto.SHA256
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Argon2() *Argon2Config {
|
|
||||||
if c == nil || c.Argon2Config == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Argon2Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodedCount get encoded count
|
|
||||||
func (c *Config) EncodedCount() uint8 {
|
|
||||||
if c == nil || c.S2KCount == 0 {
|
|
||||||
return 224 // The common case. Corresponding to 16777216
|
|
||||||
}
|
|
||||||
|
|
||||||
i := c.S2KCount
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case i < 65536:
|
|
||||||
i = 65536
|
|
||||||
case i > 65011712:
|
|
||||||
i = 65011712
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeCount(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Argon2Config) Passes() uint8 {
|
|
||||||
if c == nil || c.NumberOfPasses == 0 {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
return c.NumberOfPasses
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Argon2Config) Parallelism() uint8 {
|
|
||||||
if c == nil || c.DegreeOfParallelism == 0 {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
return c.DegreeOfParallelism
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Argon2Config) EncodedMemory() uint8 {
|
|
||||||
if c == nil || c.Memory == 0 {
|
|
||||||
return 16 // 64 MiB of RAM
|
|
||||||
}
|
|
||||||
|
|
||||||
memory := c.Memory
|
|
||||||
lowerBound := uint32(c.Parallelism())*8
|
|
||||||
upperBound := uint32(2147483648)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case memory < lowerBound:
|
|
||||||
memory = lowerBound
|
|
||||||
case memory > upperBound:
|
|
||||||
memory = upperBound
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeMemory(memory, c.Parallelism())
|
|
||||||
}
|
|
@ -1,583 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package openpgp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DetachSign signs message with the private key from signer (which must
|
|
||||||
// already have been decrypted) and writes the signature to w.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
|
|
||||||
return detachSign(w, signer, message, packet.SigTypeBinary, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArmoredDetachSign signs message with the private key from signer (which
|
|
||||||
// must already have been decrypted) and writes an armored signature to w.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) {
|
|
||||||
return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetachSignText signs message (after canonicalising the line endings) with
|
|
||||||
// the private key from signer (which must already have been decrypted) and
|
|
||||||
// writes the signature to w.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
|
|
||||||
return detachSign(w, signer, message, packet.SigTypeText, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArmoredDetachSignText signs message (after canonicalising the line endings)
|
|
||||||
// with the private key from signer (which must already have been decrypted)
|
|
||||||
// and writes an armored signature to w.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
|
|
||||||
return armoredDetachSign(w, signer, message, packet.SigTypeText, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
|
|
||||||
out, err := armor.Encode(w, SignatureType, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = detachSign(out, signer, message, sigType, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return out.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
|
|
||||||
signingKey, ok := signer.SigningKeyById(config.Now(), config.SigningKey())
|
|
||||||
if !ok {
|
|
||||||
return errors.InvalidArgumentError("no valid signing keys")
|
|
||||||
}
|
|
||||||
if signingKey.PrivateKey == nil {
|
|
||||||
return errors.InvalidArgumentError("signing key doesn't have a private key")
|
|
||||||
}
|
|
||||||
if signingKey.PrivateKey.Encrypted {
|
|
||||||
return errors.InvalidArgumentError("signing key is encrypted")
|
|
||||||
}
|
|
||||||
if _, ok := algorithm.HashToHashId(config.Hash()); !ok {
|
|
||||||
return errors.InvalidArgumentError("invalid hash function")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := createSignaturePacket(signingKey.PublicKey, sigType, config)
|
|
||||||
|
|
||||||
h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = io.Copy(wrappedHash, message); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sig.Sign(h, signingKey.PrivateKey, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return sig.Serialize(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileHints contains metadata about encrypted files. This metadata is, itself,
|
|
||||||
// encrypted.
|
|
||||||
type FileHints struct {
|
|
||||||
// IsBinary can be set to hint that the contents are binary data.
|
|
||||||
IsBinary bool
|
|
||||||
// FileName hints at the name of the file that should be written. It's
|
|
||||||
// truncated to 255 bytes if longer. It may be empty to suggest that the
|
|
||||||
// file should not be written to disk. It may be equal to "_CONSOLE" to
|
|
||||||
// suggest the data should not be written to disk.
|
|
||||||
FileName string
|
|
||||||
// ModTime contains the modification time of the file, or the zero time if not applicable.
|
|
||||||
ModTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
|
|
||||||
// The resulting WriteCloser must be closed after the contents of the file have
|
|
||||||
// been written.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
if hints == nil {
|
|
||||||
hints = &FileHints{}
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var w io.WriteCloser
|
|
||||||
cipherSuite := packet.CipherSuite{
|
|
||||||
Cipher: config.Cipher(),
|
|
||||||
Mode: config.AEAD().Mode(),
|
|
||||||
}
|
|
||||||
w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), config.AEAD() != nil, cipherSuite, key, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
literalData := w
|
|
||||||
if algo := config.Compression(); algo != packet.CompressionNone {
|
|
||||||
var compConfig *packet.CompressionConfig
|
|
||||||
if config != nil {
|
|
||||||
compConfig = config.CompressionConfig
|
|
||||||
}
|
|
||||||
literalData, err = packet.SerializeCompressed(w, algo, compConfig)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var epochSeconds uint32
|
|
||||||
if !hints.ModTime.IsZero() {
|
|
||||||
epochSeconds = uint32(hints.ModTime.Unix())
|
|
||||||
}
|
|
||||||
return packet.SerializeLiteral(literalData, hints.IsBinary, hints.FileName, epochSeconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// intersectPreferences mutates and returns a prefix of a that contains only
|
|
||||||
// the values in the intersection of a and b. The order of a is preserved.
|
|
||||||
func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
|
|
||||||
var j int
|
|
||||||
for _, v := range a {
|
|
||||||
for _, v2 := range b {
|
|
||||||
if v == v2 {
|
|
||||||
a[j] = v
|
|
||||||
j++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a[:j]
|
|
||||||
}
|
|
||||||
|
|
||||||
// intersectPreferences mutates and returns a prefix of a that contains only
|
|
||||||
// the values in the intersection of a and b. The order of a is preserved.
|
|
||||||
func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) {
|
|
||||||
var j int
|
|
||||||
for _, v := range a {
|
|
||||||
for _, v2 := range b {
|
|
||||||
if v[0] == v2[0] && v[1] == v2[1] {
|
|
||||||
a[j] = v
|
|
||||||
j++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a[:j]
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashToHashId(h crypto.Hash) uint8 {
|
|
||||||
v, ok := algorithm.HashToHashId(h)
|
|
||||||
if !ok {
|
|
||||||
panic("tried to convert unknown hash")
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptText encrypts a message to a number of recipients and, optionally,
|
|
||||||
// signs it. Optional information is contained in 'hints', also encrypted, that
|
|
||||||
// aids the recipients in processing the message. The resulting WriteCloser
|
|
||||||
// must be closed after the contents of the file have been written. If config
|
|
||||||
// is nil, sensible defaults will be used. The signing is done in text mode.
|
|
||||||
func EncryptText(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeText, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt encrypts a message to a number of recipients and, optionally, signs
|
|
||||||
// it. hints contains optional information, that is also encrypted, that aids
|
|
||||||
// the recipients in processing the message. The resulting WriteCloser must
|
|
||||||
// be closed after the contents of the file have been written.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeBinary, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSplit encrypts a message to a number of recipients and, optionally, signs
|
|
||||||
// it. hints contains optional information, that is also encrypted, that aids
|
|
||||||
// the recipients in processing the message. The resulting WriteCloser must
|
|
||||||
// be closed after the contents of the file have been written.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func EncryptSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeBinary, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptTextSplit encrypts a message to a number of recipients and, optionally, signs
|
|
||||||
// it. hints contains optional information, that is also encrypted, that aids
|
|
||||||
// the recipients in processing the message. The resulting WriteCloser must
|
|
||||||
// be closed after the contents of the file have been written.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func EncryptTextSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeText, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeAndSign writes the data as a payload package and, optionally, signs
|
|
||||||
// it. hints contains optional information, that is also encrypted,
|
|
||||||
// that aids the recipients in processing the message. The resulting
|
|
||||||
// WriteCloser must be closed after the contents of the file have been
|
|
||||||
// written. If config is nil, sensible defaults will be used.
|
|
||||||
func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
var signer *packet.PrivateKey
|
|
||||||
if signed != nil {
|
|
||||||
signKey, ok := signed.SigningKeyById(config.Now(), config.SigningKey())
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.InvalidArgumentError("no valid signing keys")
|
|
||||||
}
|
|
||||||
signer = signKey.PrivateKey
|
|
||||||
if signer == nil {
|
|
||||||
return nil, errors.InvalidArgumentError("no private key in signing key")
|
|
||||||
}
|
|
||||||
if signer.Encrypted {
|
|
||||||
return nil, errors.InvalidArgumentError("signing key must be decrypted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash crypto.Hash
|
|
||||||
for _, hashId := range candidateHashes {
|
|
||||||
if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
|
|
||||||
hash = h
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the hash specified by config is a candidate, we'll use that.
|
|
||||||
if configuredHash := config.Hash(); configuredHash.Available() {
|
|
||||||
for _, hashId := range candidateHashes {
|
|
||||||
if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
|
|
||||||
hash = h
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hash == 0 {
|
|
||||||
hashId := candidateHashes[0]
|
|
||||||
name, ok := algorithm.HashIdToString(hashId)
|
|
||||||
if !ok {
|
|
||||||
name = "#" + strconv.Itoa(int(hashId))
|
|
||||||
}
|
|
||||||
return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if signer != nil {
|
|
||||||
ops := &packet.OnePassSignature{
|
|
||||||
SigType: sigType,
|
|
||||||
Hash: hash,
|
|
||||||
PubKeyAlgo: signer.PubKeyAlgo,
|
|
||||||
KeyId: signer.KeyId,
|
|
||||||
IsLast: true,
|
|
||||||
}
|
|
||||||
if err := ops.Serialize(payload); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hints == nil {
|
|
||||||
hints = &FileHints{}
|
|
||||||
}
|
|
||||||
|
|
||||||
w := payload
|
|
||||||
if signer != nil {
|
|
||||||
// If we need to write a signature packet after the literal
|
|
||||||
// data then we need to stop literalData from closing
|
|
||||||
// encryptedData.
|
|
||||||
w = noOpCloser{w}
|
|
||||||
|
|
||||||
}
|
|
||||||
var epochSeconds uint32
|
|
||||||
if !hints.ModTime.IsZero() {
|
|
||||||
epochSeconds = uint32(hints.ModTime.Unix())
|
|
||||||
}
|
|
||||||
literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if signer != nil {
|
|
||||||
h, wrappedHash, err := hashForSignature(hash, sigType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
metadata := &packet.LiteralData{
|
|
||||||
Format: 't',
|
|
||||||
FileName: hints.FileName,
|
|
||||||
Time: epochSeconds,
|
|
||||||
}
|
|
||||||
if hints.IsBinary {
|
|
||||||
metadata.Format = 'b'
|
|
||||||
}
|
|
||||||
return signatureWriter{payload, literalData, hash, wrappedHash, h, signer, sigType, config, metadata}, nil
|
|
||||||
}
|
|
||||||
return literalData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypt encrypts a message to a number of recipients and, optionally, signs
|
|
||||||
// it. hints contains optional information, that is also encrypted, that aids
|
|
||||||
// the recipients in processing the message. The resulting WriteCloser must
|
|
||||||
// be closed after the contents of the file have been written.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
|
|
||||||
if len(to) == 0 {
|
|
||||||
return nil, errors.InvalidArgumentError("no encryption recipient provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the possible ciphers that we'll use for the message.
|
|
||||||
candidateCiphers := []uint8{
|
|
||||||
uint8(packet.CipherAES256),
|
|
||||||
uint8(packet.CipherAES128),
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the possible hash functions that we'll use for the signature.
|
|
||||||
candidateHashes := []uint8{
|
|
||||||
hashToHashId(crypto.SHA256),
|
|
||||||
hashToHashId(crypto.SHA384),
|
|
||||||
hashToHashId(crypto.SHA512),
|
|
||||||
hashToHashId(crypto.SHA3_256),
|
|
||||||
hashToHashId(crypto.SHA3_512),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer GCM if everyone supports it
|
|
||||||
candidateCipherSuites := [][2]uint8{
|
|
||||||
{uint8(packet.CipherAES256), uint8(packet.AEADModeGCM)},
|
|
||||||
{uint8(packet.CipherAES256), uint8(packet.AEADModeEAX)},
|
|
||||||
{uint8(packet.CipherAES256), uint8(packet.AEADModeOCB)},
|
|
||||||
{uint8(packet.CipherAES128), uint8(packet.AEADModeGCM)},
|
|
||||||
{uint8(packet.CipherAES128), uint8(packet.AEADModeEAX)},
|
|
||||||
{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)},
|
|
||||||
}
|
|
||||||
|
|
||||||
candidateCompression := []uint8{
|
|
||||||
uint8(packet.CompressionNone),
|
|
||||||
uint8(packet.CompressionZIP),
|
|
||||||
uint8(packet.CompressionZLIB),
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptKeys := make([]Key, len(to))
|
|
||||||
|
|
||||||
// AEAD is used only if config enables it and every key supports it
|
|
||||||
aeadSupported := config.AEAD() != nil
|
|
||||||
|
|
||||||
for i := range to {
|
|
||||||
var ok bool
|
|
||||||
encryptKeys[i], ok = to[i].EncryptionKey(config.Now())
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := to[i].PrimaryIdentity().SelfSignature
|
|
||||||
if !sig.SEIPDv2 {
|
|
||||||
aeadSupported = false
|
|
||||||
}
|
|
||||||
|
|
||||||
candidateCiphers = intersectPreferences(candidateCiphers, sig.PreferredSymmetric)
|
|
||||||
candidateHashes = intersectPreferences(candidateHashes, sig.PreferredHash)
|
|
||||||
candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, sig.PreferredCipherSuites)
|
|
||||||
candidateCompression = intersectPreferences(candidateCompression, sig.PreferredCompression)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the event that the intersection of supported algorithms is empty we use the ones
|
|
||||||
// labelled as MUST that every implementation supports.
|
|
||||||
if len(candidateCiphers) == 0 {
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.3
|
|
||||||
candidateCiphers = []uint8{uint8(packet.CipherAES128)}
|
|
||||||
}
|
|
||||||
if len(candidateHashes) == 0 {
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#hash-algos
|
|
||||||
candidateHashes = []uint8{hashToHashId(crypto.SHA256)}
|
|
||||||
}
|
|
||||||
if len(candidateCipherSuites) == 0 {
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6
|
|
||||||
candidateCipherSuites = [][2]uint8{{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}}
|
|
||||||
}
|
|
||||||
|
|
||||||
cipher := packet.CipherFunction(candidateCiphers[0])
|
|
||||||
aeadCipherSuite := packet.CipherSuite{
|
|
||||||
Cipher: packet.CipherFunction(candidateCipherSuites[0][0]),
|
|
||||||
Mode: packet.AEADMode(candidateCipherSuites[0][1]),
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the cipher specified by config is a candidate, we'll use that.
|
|
||||||
configuredCipher := config.Cipher()
|
|
||||||
for _, c := range candidateCiphers {
|
|
||||||
cipherFunc := packet.CipherFunction(c)
|
|
||||||
if cipherFunc == configuredCipher {
|
|
||||||
cipher = cipherFunc
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
symKey := make([]byte, cipher.KeySize())
|
|
||||||
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, key := range encryptKeys {
|
|
||||||
if err := packet.SerializeEncryptedKey(keyWriter, key.PublicKey, cipher, symKey, config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload io.WriteCloser
|
|
||||||
payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, symKey, config)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err = handleCompression(payload, candidateCompression, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeAndSign(payload, candidateHashes, signed, hints, sigType, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs a message. The resulting WriteCloser must be closed after the
|
|
||||||
// contents of the file have been written. hints contains optional information
|
|
||||||
// that aids the recipients in processing the message.
|
|
||||||
// If config is nil, sensible defaults will be used.
|
|
||||||
func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) {
|
|
||||||
if signed == nil {
|
|
||||||
return nil, errors.InvalidArgumentError("no signer provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the possible hash functions that we'll use for the signature.
|
|
||||||
candidateHashes := []uint8{
|
|
||||||
hashToHashId(crypto.SHA256),
|
|
||||||
hashToHashId(crypto.SHA384),
|
|
||||||
hashToHashId(crypto.SHA512),
|
|
||||||
hashToHashId(crypto.SHA3_256),
|
|
||||||
hashToHashId(crypto.SHA3_512),
|
|
||||||
}
|
|
||||||
defaultHashes := candidateHashes[0:1]
|
|
||||||
preferredHashes := signed.PrimaryIdentity().SelfSignature.PreferredHash
|
|
||||||
if len(preferredHashes) == 0 {
|
|
||||||
preferredHashes = defaultHashes
|
|
||||||
}
|
|
||||||
candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
|
|
||||||
if len(candidateHashes) == 0 {
|
|
||||||
return nil, errors.InvalidArgumentError("cannot sign because signing key shares no common algorithms with candidate hashes")
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, packet.SigTypeBinary, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// signatureWriter hashes the contents of a message while passing it along to
|
|
||||||
// literalData. When closed, it closes literalData, writes a signature packet
|
|
||||||
// to encryptedData and then also closes encryptedData.
|
|
||||||
type signatureWriter struct {
|
|
||||||
encryptedData io.WriteCloser
|
|
||||||
literalData io.WriteCloser
|
|
||||||
hashType crypto.Hash
|
|
||||||
wrappedHash hash.Hash
|
|
||||||
h hash.Hash
|
|
||||||
signer *packet.PrivateKey
|
|
||||||
sigType packet.SignatureType
|
|
||||||
config *packet.Config
|
|
||||||
metadata *packet.LiteralData // V5 signatures protect document metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s signatureWriter) Write(data []byte) (int, error) {
|
|
||||||
s.wrappedHash.Write(data)
|
|
||||||
switch s.sigType {
|
|
||||||
case packet.SigTypeBinary:
|
|
||||||
return s.literalData.Write(data)
|
|
||||||
case packet.SigTypeText:
|
|
||||||
flag := 0
|
|
||||||
return writeCanonical(s.literalData, data, &flag)
|
|
||||||
}
|
|
||||||
return 0, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(s.sigType)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s signatureWriter) Close() error {
|
|
||||||
sig := createSignaturePacket(&s.signer.PublicKey, s.sigType, s.config)
|
|
||||||
sig.Hash = s.hashType
|
|
||||||
sig.Metadata = s.metadata
|
|
||||||
|
|
||||||
if err := sig.Sign(s.h, s.signer, s.config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.literalData.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := sig.Serialize(s.encryptedData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.encryptedData.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature {
|
|
||||||
sigLifetimeSecs := config.SigLifetime()
|
|
||||||
return &packet.Signature{
|
|
||||||
Version: signer.Version,
|
|
||||||
SigType: sigType,
|
|
||||||
PubKeyAlgo: signer.PubKeyAlgo,
|
|
||||||
Hash: config.Hash(),
|
|
||||||
CreationTime: config.Now(),
|
|
||||||
IssuerKeyId: &signer.KeyId,
|
|
||||||
IssuerFingerprint: signer.Fingerprint,
|
|
||||||
Notations: config.Notations(),
|
|
||||||
SigLifetimeSecs: &sigLifetimeSecs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
|
|
||||||
// TODO: we have two of these in OpenPGP packages alone. This probably needs
|
|
||||||
// to be promoted somewhere more common.
|
|
||||||
type noOpCloser struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c noOpCloser) Write(data []byte) (n int, err error) {
|
|
||||||
return c.w.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c noOpCloser) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, config *packet.Config) (data io.WriteCloser, err error) {
|
|
||||||
data = compressed
|
|
||||||
confAlgo := config.Compression()
|
|
||||||
if confAlgo == packet.CompressionNone {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set algorithm labelled as MUST as fallback
|
|
||||||
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.4
|
|
||||||
finalAlgo := packet.CompressionNone
|
|
||||||
// if compression specified by config available we will use it
|
|
||||||
for _, c := range candidateCompression {
|
|
||||||
if uint8(confAlgo) == c {
|
|
||||||
finalAlgo = confAlgo
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if finalAlgo != packet.CompressionNone {
|
|
||||||
var compConfig *packet.CompressionConfig
|
|
||||||
if config != nil {
|
|
||||||
compConfig = config.CompressionConfig
|
|
||||||
}
|
|
||||||
data, err = packet.SerializeCompressed(compressed, finalAlgo, compConfig)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
vendor
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2019 Proton Technologies AG
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to
|
|
||||||
deal in the Software without restriction, including without limitation the
|
|
||||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
@ -1,25 +0,0 @@
|
|||||||
# Go Mime Wrapper Library
|
|
||||||
|
|
||||||
Provides a parser for MIME messages
|
|
||||||
|
|
||||||
## Download/Install
|
|
||||||
|
|
||||||
Run `go get -u github.com/ProtonMail/go-mime`, or manually `git clone` the
|
|
||||||
repository into `$GOPATH/src/github.com/ProtonMail/go-mime`.
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
The library can be used to extract the body and attachments from a MIME message
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```go
|
|
||||||
printAccepter := gomime.NewMIMEPrinter()
|
|
||||||
bodyCollector := gomime.NewBodyCollector(printAccepter)
|
|
||||||
attachmentsCollector := gomime.NewAttachmentsCollector(bodyCollector)
|
|
||||||
mimeVisitor := gomime.NewMimeVisitor(attachmentsCollector)
|
|
||||||
err := gomime.VisitAll(bytes.NewReader(mmBodyData), h, mimeVisitor)
|
|
||||||
attachments := attachmentsCollector.GetAttachments(),
|
|
||||||
attachmentsHeaders := attachmentsCollector.GetAttHeaders()
|
|
||||||
bodyContent, bodyMimeType := bodyCollector.GetBody()
|
|
||||||
```
|
|
@ -1,213 +0,0 @@
|
|||||||
package gomime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"mime/quotedprintable"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
"golang.org/x/text/encoding/htmlindex"
|
|
||||||
)
|
|
||||||
|
|
||||||
var wordDec = &mime.WordDecoder{
|
|
||||||
CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
|
|
||||||
dec, err := selectDecoder(charset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if dec == nil { // utf-8
|
|
||||||
return input, nil
|
|
||||||
}
|
|
||||||
return dec.Reader(input), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// expected trimmed low case
|
|
||||||
func getEncoding(charset string) (enc encoding.Encoding, err error) {
|
|
||||||
preparsed := strings.Trim(strings.ToLower(charset), " \t\r\n")
|
|
||||||
|
|
||||||
// koi
|
|
||||||
re := regexp.MustCompile("(cs)?koi[-_ ]?8?[-_ ]?(r|ru|u|uk)?$")
|
|
||||||
matches := re.FindAllStringSubmatch(preparsed, -1)
|
|
||||||
if len(matches) == 1 && len(matches[0]) == 3 {
|
|
||||||
preparsed = "koi8-"
|
|
||||||
switch matches[0][2] {
|
|
||||||
case "u", "uk":
|
|
||||||
preparsed += "u"
|
|
||||||
default:
|
|
||||||
preparsed += "r"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// windows-XXXX
|
|
||||||
re = regexp.MustCompile("(cp|(cs)?win(dows)?)[-_ ]?([0-9]{3,4})$")
|
|
||||||
matches = re.FindAllStringSubmatch(preparsed, -1)
|
|
||||||
if len(matches) == 1 && len(matches[0]) == 5 {
|
|
||||||
switch matches[0][4] {
|
|
||||||
case "874", "1250", "1251", "1252", "1253", "1254", "1255", "1256", "1257", "1258":
|
|
||||||
preparsed = "windows-" + matches[0][4]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// iso
|
|
||||||
re = regexp.MustCompile("iso[-_ ]?([0-9]{4})[-_ ]?([0-9]+|jp)?[-_ ]?(i|e)?")
|
|
||||||
matches = re.FindAllStringSubmatch(preparsed, -1)
|
|
||||||
if len(matches) == 1 && len(matches[0]) == 4 {
|
|
||||||
if matches[0][1] == "2022" && matches[0][2] == "jp" {
|
|
||||||
preparsed = "iso-2022-jp"
|
|
||||||
}
|
|
||||||
if matches[0][1] == "8859" {
|
|
||||||
switch matches[0][2] {
|
|
||||||
case "1", "2", "3", "4", "5", "7", "8", "9", "10", "11", "13", "14", "15", "16":
|
|
||||||
preparsed = "iso-8859-" + matches[0][2]
|
|
||||||
if matches[0][3] == "i" {
|
|
||||||
preparsed += "-" + matches[0][3]
|
|
||||||
}
|
|
||||||
case "":
|
|
||||||
preparsed = "iso-8859-1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// latin is tricky
|
|
||||||
re = regexp.MustCompile("^(cs|csiso)?l(atin)?[-_ ]?([0-9]{1,2})$")
|
|
||||||
matches = re.FindAllStringSubmatch(preparsed, -1)
|
|
||||||
if len(matches) == 1 && len(matches[0]) == 4 {
|
|
||||||
switch matches[0][3] {
|
|
||||||
case "1":
|
|
||||||
preparsed = "windows-1252"
|
|
||||||
case "2", "3", "4", "5":
|
|
||||||
preparsed = "iso-8859-" + matches[0][3]
|
|
||||||
case "6":
|
|
||||||
preparsed = "iso-8859-10"
|
|
||||||
case "8":
|
|
||||||
preparsed = "iso-8859-14"
|
|
||||||
case "9":
|
|
||||||
preparsed = "iso-8859-15"
|
|
||||||
case "10":
|
|
||||||
preparsed = "iso-8859-16"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// missing substitutions
|
|
||||||
switch preparsed {
|
|
||||||
case "csutf8", "iso-utf-8", "utf8mb4":
|
|
||||||
preparsed = "utf-8"
|
|
||||||
|
|
||||||
case "eucjp", "ibm-eucjp":
|
|
||||||
preparsed = "euc-jp"
|
|
||||||
case "euckr", "ibm-euckr", "cp949":
|
|
||||||
preparsed = "euc-kr"
|
|
||||||
case "euccn", "ibm-euccn":
|
|
||||||
preparsed = "gbk"
|
|
||||||
case "zht16mswin950", "cp950":
|
|
||||||
preparsed = "big5"
|
|
||||||
|
|
||||||
case "csascii",
|
|
||||||
"ansi_x3.4-1968",
|
|
||||||
"ansi_x3.4-1986",
|
|
||||||
"ansi_x3.110-1983",
|
|
||||||
"cp850",
|
|
||||||
"cp858",
|
|
||||||
"us",
|
|
||||||
"iso646",
|
|
||||||
"iso-646",
|
|
||||||
"iso646-us",
|
|
||||||
"iso_646.irv:1991",
|
|
||||||
"cp367",
|
|
||||||
"ibm367",
|
|
||||||
"ibm-367",
|
|
||||||
"iso-ir-6":
|
|
||||||
preparsed = "ascii"
|
|
||||||
|
|
||||||
case "ibm852":
|
|
||||||
preparsed = "iso-8859-2"
|
|
||||||
case "iso-ir-199", "iso-celtic":
|
|
||||||
preparsed = "iso-8859-14"
|
|
||||||
case "iso-ir-226":
|
|
||||||
preparsed = "iso-8859-16"
|
|
||||||
|
|
||||||
case "macroman":
|
|
||||||
preparsed = "macintosh"
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, _ = htmlindex.Get(preparsed)
|
|
||||||
if enc == nil {
|
|
||||||
err = fmt.Errorf("can not get encodig for '%s' (or '%s')", charset, preparsed)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectDecoder(charset string) (decoder *encoding.Decoder, err error) {
|
|
||||||
var enc encoding.Encoding
|
|
||||||
lcharset := strings.Trim(strings.ToLower(charset), " \t\r\n")
|
|
||||||
switch lcharset {
|
|
||||||
case "utf7", "utf-7", "unicode-1-1-utf-7":
|
|
||||||
return NewUtf7Decoder(), nil
|
|
||||||
default:
|
|
||||||
enc, err = getEncoding(lcharset)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
decoder = enc.NewDecoder()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeHeader if needed. Returns error if raw contains non-utf8 characters
|
|
||||||
func DecodeHeader(raw string) (decoded string, err error) {
|
|
||||||
if decoded, err = wordDec.DecodeHeader(raw); err != nil {
|
|
||||||
decoded = raw
|
|
||||||
}
|
|
||||||
if !utf8.ValidString(decoded) {
|
|
||||||
err = fmt.Errorf("header contains non utf8 chars: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeHeader using quoted printable and utf8
|
|
||||||
func EncodeHeader(s string) string {
|
|
||||||
return mime.QEncoding.Encode("utf-8", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeCharset decodes the orginal using content type parameters. When
|
|
||||||
// charset missing it checks the content is utf8-valid.
|
|
||||||
func DecodeCharset(original []byte, mediaType string, contentTypeParams map[string]string) ([]byte, error) {
|
|
||||||
var decoder *encoding.Decoder
|
|
||||||
var err error
|
|
||||||
if charset, ok := contentTypeParams["charset"]; ok {
|
|
||||||
decoder, err = selectDecoder(charset)
|
|
||||||
} else {
|
|
||||||
if !strings.HasPrefix(mediaType, "text/") || utf8.Valid(original) {
|
|
||||||
return original, nil
|
|
||||||
}
|
|
||||||
err = fmt.Errorf("non-utf8 content without charset specification")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return original, err
|
|
||||||
}
|
|
||||||
utf8, err := decoder.Bytes(original)
|
|
||||||
if err != nil {
|
|
||||||
return original, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return utf8, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeContentEncoding wraps the reader with decoder based on content encoding
|
|
||||||
func DecodeContentEncoding(r io.Reader, contentEncoding string) (d io.Reader) {
|
|
||||||
switch strings.ToLower(contentEncoding) {
|
|
||||||
case "quoted-printable":
|
|
||||||
d = quotedprintable.NewReader(r)
|
|
||||||
case "base64":
|
|
||||||
d = base64.NewDecoder(base64.StdEncoding, r)
|
|
||||||
case "7bit", "8bit", "binary", "": // Nothing to do
|
|
||||||
d = r
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,526 +0,0 @@
|
|||||||
package gomime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"mime"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/mail"
|
|
||||||
"net/textproto"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VisitAcceptor decidest what to do with part which is processed
|
|
||||||
// It is used by MIMEVisitor
|
|
||||||
type VisitAcceptor interface {
|
|
||||||
Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func VisitAll(part io.Reader, h textproto.MIMEHeader, accepter VisitAcceptor) (err error) {
|
|
||||||
mediaType, _, err := getContentType(h)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return accepter.Accept(part, h, mediaType == "text/plain", true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsLeaf(h textproto.MIMEHeader) bool {
|
|
||||||
return !strings.HasPrefix(h.Get("Content-Type"), "multipart/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MIMEVisitor is main object to parse (visit) and process (accept) all parts of MIME message
|
|
||||||
type MimeVisitor struct {
|
|
||||||
target VisitAcceptor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept reads part recursively if needed
|
|
||||||
// hasPlainSibling is there when acceptor want to check alternatives
|
|
||||||
func (mv *MimeVisitor) Accept(part io.Reader, h textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) {
|
|
||||||
if !isFirst {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parentMediaType, params, err := getContentType(h)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = mv.target.Accept(part, h, hasPlainSibling, true, false); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !IsLeaf(h) {
|
|
||||||
var multiparts []io.Reader
|
|
||||||
var multipartHeaders []textproto.MIMEHeader
|
|
||||||
if multiparts, multipartHeaders, err = GetMultipartParts(part, params); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hasPlainChild := false
|
|
||||||
for _, header := range multipartHeaders {
|
|
||||||
mediaType, _, _ := getContentType(header)
|
|
||||||
if mediaType == "text/plain" {
|
|
||||||
hasPlainChild = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasPlainSibling && parentMediaType == "multipart/related" {
|
|
||||||
hasPlainChild = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, p := range multiparts {
|
|
||||||
if err = mv.Accept(p, multipartHeaders[i], hasPlainChild, true, true); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = mv.target.Accept(part, h, hasPlainSibling, false, i == (len(multiparts)-1)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMIMEVisitor initialiazed with acceptor
|
|
||||||
func NewMimeVisitor(targetAccepter VisitAcceptor) *MimeVisitor {
|
|
||||||
return &MimeVisitor{targetAccepter}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRawMimePart(rawdata io.Reader, boundary string) (io.Reader, io.Reader) {
|
|
||||||
b, _ := ioutil.ReadAll(rawdata)
|
|
||||||
tee := bytes.NewReader(b)
|
|
||||||
|
|
||||||
reader := bufio.NewReader(bytes.NewReader(b))
|
|
||||||
byteBoundary := []byte(boundary)
|
|
||||||
bodyBuffer := &bytes.Buffer{}
|
|
||||||
for {
|
|
||||||
line, _, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return tee, bytes.NewReader(bodyBuffer.Bytes())
|
|
||||||
}
|
|
||||||
if bytes.HasPrefix(line, byteBoundary) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lineEndingLength := 0
|
|
||||||
for {
|
|
||||||
line, isPrefix, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return tee, bytes.NewReader(bodyBuffer.Bytes())
|
|
||||||
}
|
|
||||||
if bytes.HasPrefix(line, byteBoundary) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
lineEndingLength = 0
|
|
||||||
bodyBuffer.Write(line)
|
|
||||||
if !isPrefix {
|
|
||||||
reader.UnreadByte()
|
|
||||||
reader.UnreadByte()
|
|
||||||
token, _ := reader.ReadByte()
|
|
||||||
if token == '\r' {
|
|
||||||
lineEndingLength++
|
|
||||||
bodyBuffer.WriteByte(token)
|
|
||||||
}
|
|
||||||
lineEndingLength++
|
|
||||||
bodyBuffer.WriteByte(token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ioutil.ReadAll(reader)
|
|
||||||
data := bodyBuffer.Bytes()
|
|
||||||
return tee, bytes.NewReader(data[0 : len(data)-lineEndingLength])
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllChildParts(part io.Reader, h textproto.MIMEHeader) (parts []io.Reader, headers []textproto.MIMEHeader, err error) {
|
|
||||||
mediaType, params, err := getContentType(h)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(mediaType, "multipart/") {
|
|
||||||
var multiparts []io.Reader
|
|
||||||
var multipartHeaders []textproto.MIMEHeader
|
|
||||||
if multiparts, multipartHeaders, err = GetMultipartParts(part, params); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.Contains(mediaType, "alternative") {
|
|
||||||
var chosenPart io.Reader
|
|
||||||
var chosenHeader textproto.MIMEHeader
|
|
||||||
if chosenPart, chosenHeader, err = pickAlternativePart(multiparts, multipartHeaders); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var childParts []io.Reader
|
|
||||||
var childHeaders []textproto.MIMEHeader
|
|
||||||
if childParts, childHeaders, err = GetAllChildParts(chosenPart, chosenHeader); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parts = append(parts, childParts...)
|
|
||||||
headers = append(headers, childHeaders...)
|
|
||||||
} else {
|
|
||||||
for i, p := range multiparts {
|
|
||||||
var childParts []io.Reader
|
|
||||||
var childHeaders []textproto.MIMEHeader
|
|
||||||
if childParts, childHeaders, err = GetAllChildParts(p, multipartHeaders[i]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parts = append(parts, childParts...)
|
|
||||||
headers = append(headers, childHeaders...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parts = append(parts, part)
|
|
||||||
headers = append(headers, h)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMultipartParts(r io.Reader, params map[string]string) (parts []io.Reader, headers []textproto.MIMEHeader, err error) {
|
|
||||||
mr := multipart.NewReader(r, params["boundary"])
|
|
||||||
parts = []io.Reader{}
|
|
||||||
headers = []textproto.MIMEHeader{}
|
|
||||||
var p *multipart.Part
|
|
||||||
for {
|
|
||||||
p, err = mr.NextRawPart()
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b, _ := ioutil.ReadAll(p)
|
|
||||||
buffer := bytes.NewBuffer(b)
|
|
||||||
|
|
||||||
parts = append(parts, buffer)
|
|
||||||
headers = append(headers, p.Header)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickAlternativePart(parts []io.Reader, headers []textproto.MIMEHeader) (part io.Reader, h textproto.MIMEHeader, err error) {
|
|
||||||
|
|
||||||
for i, h := range headers {
|
|
||||||
mediaType, _, err := getContentType(h)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(mediaType, "multipart/") {
|
|
||||||
return parts[i], headers[i], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, h := range headers {
|
|
||||||
mediaType, _, err := getContentType(h)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if mediaType == "text/html" {
|
|
||||||
return parts[i], headers[i], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, h := range headers {
|
|
||||||
mediaType, _, err := getContentType(h)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if mediaType == "text/plain" {
|
|
||||||
return parts[i], headers[i], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//if we get all the way here, part will be nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse address comment as defined in http://tools.wordtothewise.com/rfc/822
|
|
||||||
// FIXME: Does not work for address groups
|
|
||||||
// NOTE: This should be removed for go>1.10 (please check)
|
|
||||||
func parseAddressComment(raw string) string {
|
|
||||||
parsed := []string{}
|
|
||||||
for _, item := range regexp.MustCompile("[,;]").Split(raw, -1) {
|
|
||||||
re := regexp.MustCompile("[(][^)]*[)]")
|
|
||||||
comments := strings.Join(re.FindAllString(item, -1), " ")
|
|
||||||
comments = strings.Replace(comments, "(", "", -1)
|
|
||||||
comments = strings.Replace(comments, ")", "", -1)
|
|
||||||
withoutComments := re.ReplaceAllString(item, "")
|
|
||||||
addr, err := mail.ParseAddress(withoutComments)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if addr.Name == "" {
|
|
||||||
addr.Name = comments
|
|
||||||
}
|
|
||||||
parsed = append(parsed, addr.String())
|
|
||||||
}
|
|
||||||
return strings.Join(parsed, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkHeaders(headers []textproto.MIMEHeader) bool {
|
|
||||||
foundAttachment := false
|
|
||||||
|
|
||||||
for i := 0; i < len(headers); i++ {
|
|
||||||
h := headers[i]
|
|
||||||
|
|
||||||
mediaType, _, _ := getContentType(h)
|
|
||||||
|
|
||||||
if !strings.HasPrefix(mediaType, "text/") {
|
|
||||||
foundAttachment = true
|
|
||||||
} else if foundAttachment {
|
|
||||||
//this means that there is a text part after the first attachment, so we will have to convert the body from plain->HTML
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodePart(partReader io.Reader, header textproto.MIMEHeader) (decodedPart io.Reader) {
|
|
||||||
decodedPart = DecodeContentEncoding(partReader, header.Get("Content-Transfer-Encoding"))
|
|
||||||
if decodedPart == nil {
|
|
||||||
log.Printf("Unsupported Content-Transfer-Encoding '%v'", header.Get("Content-Transfer-Encoding"))
|
|
||||||
decodedPart = partReader
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// assume 'text/plain' if missing
|
|
||||||
func getContentType(header textproto.MIMEHeader) (mediatype string, params map[string]string, err error) {
|
|
||||||
contentType := header.Get("Content-Type")
|
|
||||||
if contentType == "" {
|
|
||||||
contentType = "text/plain"
|
|
||||||
}
|
|
||||||
|
|
||||||
return mime.ParseMediaType(contentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================== MIME Printer ===================================
|
|
||||||
// Simply print resulting MIME tree into text form
|
|
||||||
// TODO to file mime_printer.go
|
|
||||||
type stack []string
|
|
||||||
|
|
||||||
func (s stack) Push(v string) stack {
|
|
||||||
return append(s, v)
|
|
||||||
}
|
|
||||||
func (s stack) Pop() (stack, string) {
|
|
||||||
l := len(s)
|
|
||||||
return s[:l-1], s[l-1]
|
|
||||||
}
|
|
||||||
func (s stack) Peek() string {
|
|
||||||
return s[len(s)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
type MIMEPrinter struct {
|
|
||||||
result *bytes.Buffer
|
|
||||||
boundaryStack stack
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMIMEPrinter() (pd *MIMEPrinter) {
|
|
||||||
return &MIMEPrinter{
|
|
||||||
result: bytes.NewBuffer([]byte("")),
|
|
||||||
boundaryStack: stack{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pd *MIMEPrinter) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) {
|
|
||||||
if isFirst {
|
|
||||||
http.Header(header).Write(pd.result)
|
|
||||||
pd.result.Write([]byte("\n"))
|
|
||||||
if IsLeaf(header) {
|
|
||||||
pd.result.ReadFrom(partReader)
|
|
||||||
} else {
|
|
||||||
_, params, _ := getContentType(header)
|
|
||||||
boundary := params["boundary"]
|
|
||||||
pd.boundaryStack = pd.boundaryStack.Push(boundary)
|
|
||||||
pd.result.Write([]byte("\nThis is a multi-part message in MIME format.\n--" + boundary + "\n"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !isLast {
|
|
||||||
pd.result.Write([]byte("\n--" + pd.boundaryStack.Peek() + "\n"))
|
|
||||||
} else {
|
|
||||||
var boundary string
|
|
||||||
pd.boundaryStack, boundary = pd.boundaryStack.Pop()
|
|
||||||
pd.result.Write([]byte("\n--" + boundary + "--\n.\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pd *MIMEPrinter) String() string {
|
|
||||||
return pd.result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== PlainText Collector =========================
|
|
||||||
// Collect contents of all non-attachment text/plain parts and return
|
|
||||||
// it is a string
|
|
||||||
// TODO to file collector_plaintext.go
|
|
||||||
|
|
||||||
type PlainTextCollector struct {
|
|
||||||
target VisitAcceptor
|
|
||||||
plainTextContents *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPlainTextCollector(targetAccepter VisitAcceptor) *PlainTextCollector {
|
|
||||||
return &PlainTextCollector{
|
|
||||||
target: targetAccepter,
|
|
||||||
plainTextContents: bytes.NewBuffer([]byte("")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ptc *PlainTextCollector) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) {
|
|
||||||
if isFirst {
|
|
||||||
if IsLeaf(header) {
|
|
||||||
mediaType, params, _ := getContentType(header)
|
|
||||||
disp, _, _ := mime.ParseMediaType(header.Get("Content-Disposition"))
|
|
||||||
if mediaType == "text/plain" && disp != "attachment" {
|
|
||||||
partData, _ := ioutil.ReadAll(partReader)
|
|
||||||
decodedPart := decodePart(bytes.NewReader(partData), header)
|
|
||||||
|
|
||||||
if buffer, err := ioutil.ReadAll(decodedPart); err == nil {
|
|
||||||
buffer, err = DecodeCharset(buffer, mediaType, params)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Decode charset error:", err)
|
|
||||||
err = nil // Don't fail parsing on decoding errors, use original
|
|
||||||
}
|
|
||||||
ptc.plainTextContents.Write(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ptc.target.Accept(bytes.NewReader(partData), header, hasPlainSibling, isFirst, isLast)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = ptc.target.Accept(partReader, header, hasPlainSibling, isFirst, isLast)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ptc PlainTextCollector) GetPlainText() string {
|
|
||||||
return ptc.plainTextContents.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== Body Collector ==============
|
|
||||||
// Collect contents of all non-attachment parts and return
|
|
||||||
// it as a string
|
|
||||||
// TODO to file collector_body.go
|
|
||||||
|
|
||||||
type BodyCollector struct {
|
|
||||||
target VisitAcceptor
|
|
||||||
htmlBodyBuffer *bytes.Buffer
|
|
||||||
plainBodyBuffer *bytes.Buffer
|
|
||||||
htmlHeaderBuffer *bytes.Buffer
|
|
||||||
plainHeaderBuffer *bytes.Buffer
|
|
||||||
hasHtml bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBodyCollector(targetAccepter VisitAcceptor) *BodyCollector {
|
|
||||||
return &BodyCollector{
|
|
||||||
target: targetAccepter,
|
|
||||||
htmlBodyBuffer: bytes.NewBuffer([]byte("")),
|
|
||||||
plainBodyBuffer: bytes.NewBuffer([]byte("")),
|
|
||||||
htmlHeaderBuffer: bytes.NewBuffer([]byte("")),
|
|
||||||
plainHeaderBuffer: bytes.NewBuffer([]byte("")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *BodyCollector) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) {
|
|
||||||
// TODO: collect html and plaintext - if there's html with plain sibling don't include plain/text
|
|
||||||
if isFirst {
|
|
||||||
if IsLeaf(header) {
|
|
||||||
mediaType, params, _ := getContentType(header)
|
|
||||||
disp, _, _ := mime.ParseMediaType(header.Get("Content-Disposition"))
|
|
||||||
if disp != "attachment" {
|
|
||||||
partData, _ := ioutil.ReadAll(partReader)
|
|
||||||
decodedPart := decodePart(bytes.NewReader(partData), header)
|
|
||||||
if buffer, err := ioutil.ReadAll(decodedPart); err == nil {
|
|
||||||
buffer, err = DecodeCharset(buffer, mediaType, params)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Decode charset error:", err)
|
|
||||||
err = nil // Don't fail parsing on decoding errors, use original
|
|
||||||
}
|
|
||||||
if mediaType == "text/html" {
|
|
||||||
bc.hasHtml = true
|
|
||||||
http.Header(header).Write(bc.htmlHeaderBuffer)
|
|
||||||
bc.htmlBodyBuffer.Write(buffer)
|
|
||||||
} else if mediaType == "text/plain" {
|
|
||||||
http.Header(header).Write(bc.plainHeaderBuffer)
|
|
||||||
bc.plainBodyBuffer.Write(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bc.target.Accept(bytes.NewReader(partData), header, hasPlainSibling, isFirst, isLast)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = bc.target.Accept(partReader, header, hasPlainSibling, isFirst, isLast)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *BodyCollector) GetBody() (string, string) {
|
|
||||||
if bc.hasHtml {
|
|
||||||
return bc.htmlBodyBuffer.String(), "text/html"
|
|
||||||
} else {
|
|
||||||
return bc.plainBodyBuffer.String(), "text/plain"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *BodyCollector) GetHeaders() string {
|
|
||||||
if bc.hasHtml {
|
|
||||||
return bc.htmlHeaderBuffer.String()
|
|
||||||
} else {
|
|
||||||
return bc.plainHeaderBuffer.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== Attachments Collector ==============
|
|
||||||
// Collect contents of all attachment parts and return
|
|
||||||
// them as a string
|
|
||||||
// TODO to file collector_attachment.go
|
|
||||||
|
|
||||||
type AttachmentsCollector struct {
|
|
||||||
target VisitAcceptor
|
|
||||||
attBuffers []string
|
|
||||||
attHeaders []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAttachmentsCollector(targetAccepter VisitAcceptor) *AttachmentsCollector {
|
|
||||||
return &AttachmentsCollector{
|
|
||||||
target: targetAccepter,
|
|
||||||
attBuffers: []string{},
|
|
||||||
attHeaders: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AttachmentsCollector) Accept(partReader io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) {
|
|
||||||
if isFirst {
|
|
||||||
if IsLeaf(header) {
|
|
||||||
mediaType, params, _ := getContentType(header)
|
|
||||||
disp, _, _ := mime.ParseMediaType(header.Get("Content-Disposition"))
|
|
||||||
if (mediaType != "text/html" && mediaType != "text/plain") || disp == "attachment" {
|
|
||||||
partData, _ := ioutil.ReadAll(partReader)
|
|
||||||
decodedPart := decodePart(bytes.NewReader(partData), header)
|
|
||||||
|
|
||||||
if buffer, err := ioutil.ReadAll(decodedPart); err == nil {
|
|
||||||
buffer, err = DecodeCharset(buffer, mediaType, params)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Decode charset error:", err)
|
|
||||||
err = nil // Don't fail parsing on decoding errors, use original
|
|
||||||
}
|
|
||||||
headerBuf := new(bytes.Buffer)
|
|
||||||
http.Header(header).Write(headerBuf)
|
|
||||||
ac.attHeaders = append(ac.attHeaders, headerBuf.String())
|
|
||||||
ac.attBuffers = append(ac.attBuffers, string(buffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ac.target.Accept(bytes.NewReader(partData), header, hasPlainSibling, isFirst, isLast)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = ac.target.Accept(partReader, header, hasPlainSibling, isFirst, isLast)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac AttachmentsCollector) GetAttachments() []string {
|
|
||||||
return ac.attBuffers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac AttachmentsCollector) GetAttHeaders() []string {
|
|
||||||
return ac.attHeaders
|
|
||||||
}
|
|
@ -1,170 +0,0 @@
|
|||||||
package gomime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"unicode/utf16"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
"golang.org/x/text/transform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// utf7Decoder copied from: https://github.com/cention-sany/utf7/blob/master/utf7.go
|
|
||||||
// We need `encoding.Decoder` instead of function `UTF7DecodeBytes`
|
|
||||||
type utf7Decoder struct {
|
|
||||||
transform.NopResetter
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUtf7Decoder return decoder for utf7
|
|
||||||
func NewUtf7Decoder() *encoding.Decoder {
|
|
||||||
return &encoding.Decoder{Transformer: utf7Decoder{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
uRepl = '\uFFFD' // Unicode replacement code point
|
|
||||||
u7min = 0x20 // Minimum self-representing UTF-7 value
|
|
||||||
u7max = 0x7E // Maximum self-representing UTF-7 value
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrBadUTF7 is returned to indicate the invalid modified UTF-7 encoding.
|
|
||||||
var ErrBadUTF7 = errors.New("utf7: bad utf-7 encoding")
|
|
||||||
|
|
||||||
const modifiedbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
||||||
|
|
||||||
var u7enc = base64.NewEncoding(modifiedbase64)
|
|
||||||
|
|
||||||
func isModifiedBase64(r byte) bool {
|
|
||||||
if r >= 'A' && r <= 'Z' {
|
|
||||||
return true
|
|
||||||
} else if r >= 'a' && r <= 'z' {
|
|
||||||
return true
|
|
||||||
} else if r >= '0' && r <= '9' {
|
|
||||||
return true
|
|
||||||
} else if r == '+' || r == '/' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d utf7Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
||||||
var implicit bool
|
|
||||||
var tmp int
|
|
||||||
|
|
||||||
nd, n := len(dst), len(src)
|
|
||||||
if n == 0 && !atEOF {
|
|
||||||
return 0, 0, transform.ErrShortSrc
|
|
||||||
}
|
|
||||||
for ; nSrc < n; nSrc++ {
|
|
||||||
if nDst >= nd {
|
|
||||||
return nDst, nSrc, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
if c := src[nSrc]; ((c < u7min || c > u7max) &&
|
|
||||||
c != '\t' && c != '\r' && c != '\n') ||
|
|
||||||
c == '~' || c == '\\' {
|
|
||||||
return nDst, nSrc, ErrBadUTF7 // Illegal code point in ASCII mode
|
|
||||||
} else if c != '+' {
|
|
||||||
dst[nDst] = c // character is self-representing
|
|
||||||
nDst++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// found '+'
|
|
||||||
start := nSrc + 1
|
|
||||||
tmp = nSrc // nSrc still points to '+', tmp points to the end of BASE64
|
|
||||||
// Find the end of the Base64 or "+-" segment
|
|
||||||
implicit = false
|
|
||||||
for tmp++; tmp < n && src[tmp] != '-'; tmp++ {
|
|
||||||
if !isModifiedBase64(src[tmp]) {
|
|
||||||
if tmp == start {
|
|
||||||
return nDst, tmp, ErrBadUTF7 // '+' next char must modified base64
|
|
||||||
}
|
|
||||||
// implicit shift back to ASCII - no need '-' character
|
|
||||||
implicit = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if tmp == start {
|
|
||||||
if tmp == n {
|
|
||||||
// did not find '-' sign and '+' is the last character
|
|
||||||
// total nSrc not includes '+'
|
|
||||||
if atEOF {
|
|
||||||
return nDst, nSrc, ErrBadUTF7 // '+' can not be at the end
|
|
||||||
}
|
|
||||||
// '+' can not be at the end, the source si too short
|
|
||||||
return nDst, nSrc, transform.ErrShortSrc
|
|
||||||
}
|
|
||||||
dst[nDst] = '+' // Escape sequence "+-"
|
|
||||||
nDst++
|
|
||||||
} else if tmp == n && !atEOF {
|
|
||||||
// no eof found, the source is too short
|
|
||||||
return nDst, nSrc, transform.ErrShortSrc
|
|
||||||
} else if b := utf7dec(src[start:tmp]); len(b) > 0 {
|
|
||||||
if len(b)+nDst > nd {
|
|
||||||
// need more space in dst for the decoded modified BASE64 unicode
|
|
||||||
// total nSrc is not including '+'
|
|
||||||
return nDst, nSrc, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
copy(dst[nDst:], b) // Control or non-ASCII code points in Base64
|
|
||||||
nDst += len(b)
|
|
||||||
if implicit {
|
|
||||||
if nDst >= nd {
|
|
||||||
return nDst, tmp, transform.ErrShortDst
|
|
||||||
}
|
|
||||||
dst[nDst] = src[tmp] // implicit shift
|
|
||||||
nDst++
|
|
||||||
}
|
|
||||||
if tmp == n {
|
|
||||||
return nDst, tmp, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nDst, nSrc, ErrBadUTF7 // bad encoding
|
|
||||||
}
|
|
||||||
nSrc = tmp
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// utf7dec extracts UTF-16-BE bytes from Base64 data and converts them to UTF-8.
|
|
||||||
// A nil slice is returned if the encoding is invalid.
|
|
||||||
func utf7dec(b64 []byte) []byte {
|
|
||||||
var b []byte
|
|
||||||
|
|
||||||
// Allocate a single block of memory large enough to store the Base64 data
|
|
||||||
// (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes.
|
|
||||||
// Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence,
|
|
||||||
// double the space allocation for UTF-8.
|
|
||||||
if n := len(b64); b64[n-1] == '=' {
|
|
||||||
return nil
|
|
||||||
} else if n&3 == 0 {
|
|
||||||
b = make([]byte, u7enc.DecodedLen(n)*3)
|
|
||||||
} else {
|
|
||||||
n += 4 - n&3
|
|
||||||
b = make([]byte, n+u7enc.DecodedLen(n)*3)
|
|
||||||
copy(b[copy(b, b64):n], []byte("=="))
|
|
||||||
b64, b = b[:n], b[n:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode Base64 into the first 1/3rd of b
|
|
||||||
n, err := u7enc.Decode(b, b64)
|
|
||||||
if err != nil || n&1 == 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode UTF-16-BE into the remaining 2/3rds of b
|
|
||||||
b, s := b[:n], b[n:]
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < n; i += 2 {
|
|
||||||
r := rune(b[i])<<8 | rune(b[i+1])
|
|
||||||
if utf16.IsSurrogate(r) {
|
|
||||||
if i += 2; i == n {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r2 := rune(b[i])<<8 | rune(b[i+1])
|
|
||||||
if r = utf16.DecodeRune(r, r2); r == uRepl {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j += utf8.EncodeRune(s[j:], r)
|
|
||||||
}
|
|
||||||
return s[:j]
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2019 Proton Technologies AG
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to
|
|
||||||
deal in the Software without restriction, including without limitation the
|
|
||||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
@ -1,69 +0,0 @@
|
|||||||
// Package armor contains a set of helper methods for armoring and unarmoring
|
|
||||||
// data.
|
|
||||||
package armor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/internal"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ArmorKey armors input as a public key.
|
|
||||||
func ArmorKey(input []byte) (string, error) {
|
|
||||||
return ArmorWithType(input, constants.PublicKeyHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArmorWithTypeBuffered returns a io.WriteCloser which, when written to, writes
|
|
||||||
// armored data to w with the given armorType.
|
|
||||||
func ArmorWithTypeBuffered(w io.Writer, armorType string) (io.WriteCloser, error) {
|
|
||||||
return armor.Encode(w, armorType, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArmorWithType armors input with the given armorType.
|
|
||||||
func ArmorWithType(input []byte, armorType string) (string, error) {
|
|
||||||
return armorWithTypeAndHeaders(input, armorType, internal.ArmorHeaders)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArmorWithTypeAndCustomHeaders armors input with the given armorType and
|
|
||||||
// headers.
|
|
||||||
func ArmorWithTypeAndCustomHeaders(input []byte, armorType, version, comment string) (string, error) {
|
|
||||||
headers := make(map[string]string)
|
|
||||||
if version != "" {
|
|
||||||
headers["Version"] = version
|
|
||||||
}
|
|
||||||
if comment != "" {
|
|
||||||
headers["Comment"] = comment
|
|
||||||
}
|
|
||||||
return armorWithTypeAndHeaders(input, armorType, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unarmor unarmors an armored input into a byte array.
|
|
||||||
func Unarmor(input string) ([]byte, error) {
|
|
||||||
b, err := internal.Unarmor(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopengp: unable to unarmor")
|
|
||||||
}
|
|
||||||
return ioutil.ReadAll(b.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func armorWithTypeAndHeaders(input []byte, armorType string, headers map[string]string) (string, error) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
w, err := armor.Encode(&b, armorType, headers)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "gopengp: unable to encode armoring")
|
|
||||||
}
|
|
||||||
if _, err = w.Write(input); err != nil {
|
|
||||||
return "", errors.Wrap(err, "gopengp: unable to write armored to buffer")
|
|
||||||
}
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
return "", errors.Wrap(err, "gopengp: unable to close armor buffer")
|
|
||||||
}
|
|
||||||
return b.String(), nil
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
// Package constants provides a set of common OpenPGP constants.
|
|
||||||
package constants
|
|
||||||
|
|
||||||
// Constants for armored data.
|
|
||||||
const (
|
|
||||||
ArmorHeaderVersion = "GopenPGP 2.7.5"
|
|
||||||
ArmorHeaderComment = "https://gopenpgp.org"
|
|
||||||
PGPMessageHeader = "PGP MESSAGE"
|
|
||||||
PGPSignatureHeader = "PGP SIGNATURE"
|
|
||||||
PublicKeyHeader = "PGP PUBLIC KEY BLOCK"
|
|
||||||
PrivateKeyHeader = "PGP PRIVATE KEY BLOCK"
|
|
||||||
)
|
|
@ -1,22 +0,0 @@
|
|||||||
package constants
|
|
||||||
|
|
||||||
// Cipher suite names.
|
|
||||||
const (
|
|
||||||
ThreeDES = "3des"
|
|
||||||
TripleDES = "tripledes" // Both "3des" and "tripledes" refer to 3DES.
|
|
||||||
CAST5 = "cast5"
|
|
||||||
AES128 = "aes128"
|
|
||||||
AES192 = "aes192"
|
|
||||||
AES256 = "aes256"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SIGNATURE_OK int = 0
|
|
||||||
SIGNATURE_NOT_SIGNED int = 1
|
|
||||||
SIGNATURE_NO_VERIFIER int = 2
|
|
||||||
SIGNATURE_FAILED int = 3
|
|
||||||
SIGNATURE_BAD_CONTEXT int = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultCompression = 2 // ZLIB
|
|
||||||
const DefaultCompressionLevel = 6 // Corresponds to default -1 for ZLIB
|
|
@ -1,3 +0,0 @@
|
|||||||
package constants
|
|
||||||
|
|
||||||
const SignatureContextName = "context@proton.ch"
|
|
@ -1,3 +0,0 @@
|
|||||||
package constants
|
|
||||||
|
|
||||||
const Version = "2.7.5"
|
|
@ -1,185 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AttachmentProcessor keeps track of the progress of encrypting an attachment
|
|
||||||
// (optimized for encrypting large files).
|
|
||||||
type AttachmentProcessor struct {
|
|
||||||
w *io.WriteCloser
|
|
||||||
pipe *io.PipeWriter
|
|
||||||
done sync.WaitGroup
|
|
||||||
split *PGPSplitMessage
|
|
||||||
garbageCollector int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process writes attachment data to be encrypted.
|
|
||||||
func (ap *AttachmentProcessor) Process(plainData []byte) {
|
|
||||||
if _, err := (*ap.w).Write(plainData); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if ap.garbageCollector > 0 {
|
|
||||||
defer runtime.GC()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish closes the attachment and returns the encrypted data.
|
|
||||||
func (ap *AttachmentProcessor) Finish() (*PGPSplitMessage, error) {
|
|
||||||
if ap.err != nil {
|
|
||||||
return nil, ap.err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := (*ap.w).Close(); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopengpp: unable to close writer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ap.garbageCollector > 0 {
|
|
||||||
ap.w = nil
|
|
||||||
runtime.GC()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := (*ap.pipe).Close(); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopengpp: unable to close pipe")
|
|
||||||
}
|
|
||||||
|
|
||||||
ap.done.Wait()
|
|
||||||
if ap.err != nil {
|
|
||||||
return nil, ap.err
|
|
||||||
}
|
|
||||||
splitMsg := ap.split
|
|
||||||
|
|
||||||
if ap.garbageCollector > 0 {
|
|
||||||
ap.pipe = nil
|
|
||||||
ap.split = nil
|
|
||||||
defer runtime.GC()
|
|
||||||
}
|
|
||||||
return splitMsg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newAttachmentProcessor creates an AttachmentProcessor which can be used to encrypt
|
|
||||||
// a file. It takes an estimatedSize and fileName as hints about the file.
|
|
||||||
func (keyRing *KeyRing) newAttachmentProcessor(
|
|
||||||
estimatedSize int, filename string, isBinary bool, modTime uint32, garbageCollector int, //nolint:unparam
|
|
||||||
) (*AttachmentProcessor, error) {
|
|
||||||
attachmentProc := &AttachmentProcessor{}
|
|
||||||
// You could also add these one at a time if needed.
|
|
||||||
attachmentProc.done.Add(1)
|
|
||||||
attachmentProc.garbageCollector = garbageCollector
|
|
||||||
|
|
||||||
hints := &openpgp.FileHints{
|
|
||||||
FileName: filename,
|
|
||||||
IsBinary: isBinary,
|
|
||||||
ModTime: time.Unix(int64(modTime), 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &packet.Config{
|
|
||||||
DefaultCipher: packet.CipherAES256,
|
|
||||||
Time: getTimeGenerator(),
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer attachmentProc.done.Done()
|
|
||||||
ciphertext, _ := ioutil.ReadAll(reader)
|
|
||||||
message := &PGPMessage{
|
|
||||||
Data: ciphertext,
|
|
||||||
}
|
|
||||||
split, splitError := message.SplitMessage()
|
|
||||||
if attachmentProc.err == nil {
|
|
||||||
attachmentProc.err = splitError
|
|
||||||
}
|
|
||||||
attachmentProc.split = split
|
|
||||||
}()
|
|
||||||
|
|
||||||
var ew io.WriteCloser
|
|
||||||
var encryptErr error
|
|
||||||
ew, encryptErr = openpgp.Encrypt(writer, keyRing.entities, nil, hints, config)
|
|
||||||
if encryptErr != nil {
|
|
||||||
return nil, errors.Wrap(encryptErr, "gopengpp: unable to encrypt attachment")
|
|
||||||
}
|
|
||||||
attachmentProc.w = &ew
|
|
||||||
attachmentProc.pipe = writer
|
|
||||||
|
|
||||||
return attachmentProc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptAttachment encrypts a file given a PlainMessage and a filename.
|
|
||||||
// If given a filename it will override the information in the PlainMessage object.
|
|
||||||
// Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data.
|
|
||||||
// Specifically designed for attachments rather than text messages.
|
|
||||||
func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, filename string) (*PGPSplitMessage, error) {
|
|
||||||
if filename == "" {
|
|
||||||
filename = message.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
ap, err := keyRing.newAttachmentProcessor(
|
|
||||||
len(message.GetBinary()),
|
|
||||||
filename,
|
|
||||||
message.IsBinary(),
|
|
||||||
message.Time,
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ap.Process(message.GetBinary())
|
|
||||||
split, err := ap.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return split, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLowMemoryAttachmentProcessor creates an AttachmentProcessor which can be used
|
|
||||||
// to encrypt a file. It takes an estimatedSize and filename as hints about the
|
|
||||||
// file. It is optimized for low-memory environments and collects garbage every
|
|
||||||
// megabyte.
|
|
||||||
func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor(
|
|
||||||
estimatedSize int, filename string,
|
|
||||||
) (*AttachmentProcessor, error) {
|
|
||||||
return keyRing.newAttachmentProcessor(estimatedSize, filename, true, uint32(GetUnixTime()), 1<<20)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptAttachment takes a PGPSplitMessage, containing a session key packet and symmetrically encrypted data
|
|
||||||
// and returns a decrypted PlainMessage
|
|
||||||
// Specifically designed for attachments rather than text messages.
|
|
||||||
func (keyRing *KeyRing) DecryptAttachment(message *PGPSplitMessage) (*PlainMessage, error) {
|
|
||||||
privKeyEntries := keyRing.entities
|
|
||||||
|
|
||||||
keyReader := bytes.NewReader(message.GetBinaryKeyPacket())
|
|
||||||
dataReader := bytes.NewReader(message.GetBinaryDataPacket())
|
|
||||||
|
|
||||||
encryptedReader := io.MultiReader(keyReader, dataReader)
|
|
||||||
|
|
||||||
config := &packet.Config{Time: getTimeGenerator()}
|
|
||||||
|
|
||||||
md, err := openpgp.ReadMessage(encryptedReader, privKeyEntries, nil, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopengpp: unable to read attachment")
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypted := md.UnverifiedBody
|
|
||||||
b, err := ioutil.ReadAll(decrypted)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopengpp: unable to read attachment body")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PlainMessage{
|
|
||||||
Data: b,
|
|
||||||
TextType: !md.LiteralData.IsBinary,
|
|
||||||
Filename: md.LiteralData.FileName,
|
|
||||||
Time: md.LiteralData.Time,
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ManualAttachmentProcessor keeps track of the progress of encrypting an attachment
|
|
||||||
// (optimized for encrypting large files).
|
|
||||||
// With this processor, the caller has to first allocate
|
|
||||||
// a buffer large enough to hold the whole data packet.
|
|
||||||
type ManualAttachmentProcessor struct {
|
|
||||||
keyPacket []byte
|
|
||||||
dataLength int
|
|
||||||
plaintextWriter io.WriteCloser
|
|
||||||
ciphertextWriter *io.PipeWriter
|
|
||||||
err error
|
|
||||||
done sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeyPacket returns the key packet for the attachment.
|
|
||||||
// This should be called only after Finish() has been called.
|
|
||||||
func (ap *ManualAttachmentProcessor) GetKeyPacket() []byte {
|
|
||||||
return ap.keyPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDataLength returns the number of bytes in the DataPacket.
|
|
||||||
// This should be called only after Finish() has been called.
|
|
||||||
func (ap *ManualAttachmentProcessor) GetDataLength() int {
|
|
||||||
return ap.dataLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process writes attachment data to be encrypted.
|
|
||||||
func (ap *ManualAttachmentProcessor) Process(plainData []byte) error {
|
|
||||||
defer runtime.GC()
|
|
||||||
_, err := ap.plaintextWriter.Write(plainData)
|
|
||||||
return errors.Wrap(err, "gopenpgp: couldn't write attachment data")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish tells the processor to finalize encryption.
|
|
||||||
func (ap *ManualAttachmentProcessor) Finish() error {
|
|
||||||
defer runtime.GC()
|
|
||||||
if ap.err != nil {
|
|
||||||
return ap.err
|
|
||||||
}
|
|
||||||
if err := ap.plaintextWriter.Close(); err != nil {
|
|
||||||
return errors.Wrap(err, "gopengpp: unable to close the plaintext writer")
|
|
||||||
}
|
|
||||||
if err := ap.ciphertextWriter.Close(); err != nil {
|
|
||||||
return errors.Wrap(err, "gopengpp: unable to close the dataPacket writer")
|
|
||||||
}
|
|
||||||
ap.done.Wait()
|
|
||||||
if ap.err != nil {
|
|
||||||
return ap.err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewManualAttachmentProcessor creates an AttachmentProcessor which can be used
|
|
||||||
// to encrypt a file. It takes an estimatedSize and filename as hints about the
|
|
||||||
// file and a buffer to hold the DataPacket.
|
|
||||||
// It is optimized for low-memory environments and collects garbage every megabyte.
|
|
||||||
// The buffer for the data packet must be manually allocated by the caller.
|
|
||||||
// Make sure that the dataBuffer is large enough to hold the whole data packet
|
|
||||||
// otherwise Finish() will return an error.
|
|
||||||
func (keyRing *KeyRing) NewManualAttachmentProcessor(
|
|
||||||
estimatedSize int, filename string, dataBuffer []byte,
|
|
||||||
) (*ManualAttachmentProcessor, error) {
|
|
||||||
if len(dataBuffer) == 0 {
|
|
||||||
return nil, errors.New("gopenpgp: can't give a nil or empty buffer to process the attachment")
|
|
||||||
}
|
|
||||||
|
|
||||||
// forces the gc to be called often
|
|
||||||
debug.SetGCPercent(10)
|
|
||||||
|
|
||||||
attachmentProc := &ManualAttachmentProcessor{}
|
|
||||||
|
|
||||||
// hints for the encrypted file
|
|
||||||
isBinary := true
|
|
||||||
modTime := GetUnixTime()
|
|
||||||
hints := &openpgp.FileHints{
|
|
||||||
FileName: filename,
|
|
||||||
IsBinary: isBinary,
|
|
||||||
ModTime: time.Unix(modTime, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
// encryption config
|
|
||||||
config := &packet.Config{
|
|
||||||
DefaultCipher: packet.CipherAES256,
|
|
||||||
Time: getTimeGenerator(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// goroutine that reads the key packet
|
|
||||||
// to be later returned to the caller via GetKeyPacket()
|
|
||||||
keyReader, keyWriter := io.Pipe()
|
|
||||||
attachmentProc.done.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer attachmentProc.done.Done()
|
|
||||||
keyPacket, err := ioutil.ReadAll(keyReader)
|
|
||||||
if err != nil {
|
|
||||||
attachmentProc.err = err
|
|
||||||
} else {
|
|
||||||
attachmentProc.keyPacket = clone(keyPacket)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// goroutine that reads the data packet into the provided buffer
|
|
||||||
dataReader, dataWriter := io.Pipe()
|
|
||||||
attachmentProc.done.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer attachmentProc.done.Done()
|
|
||||||
totalRead, err := readAll(dataBuffer, dataReader)
|
|
||||||
if err != nil {
|
|
||||||
attachmentProc.err = err
|
|
||||||
} else {
|
|
||||||
attachmentProc.dataLength = totalRead
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// We generate the encrypting writer
|
|
||||||
var ew io.WriteCloser
|
|
||||||
var encryptErr error
|
|
||||||
ew, encryptErr = openpgp.EncryptSplit(keyWriter, dataWriter, keyRing.entities, nil, hints, config)
|
|
||||||
if encryptErr != nil {
|
|
||||||
return nil, errors.Wrap(encryptErr, "gopengpp: unable to encrypt attachment")
|
|
||||||
}
|
|
||||||
|
|
||||||
attachmentProc.plaintextWriter = ew
|
|
||||||
attachmentProc.ciphertextWriter = dataWriter
|
|
||||||
|
|
||||||
// The key packet should have been already written, so we can close
|
|
||||||
if err := keyWriter.Close(); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: couldn't close the keyPacket writer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the goroutines encountered errors
|
|
||||||
if attachmentProc.err != nil {
|
|
||||||
return nil, attachmentProc.err
|
|
||||||
}
|
|
||||||
return attachmentProc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readAll works a bit like ioutil.ReadAll
|
|
||||||
// but we can choose the buffer to write to
|
|
||||||
// and we don't grow the slice in case of overflow.
|
|
||||||
func readAll(buffer []byte, reader io.Reader) (int, error) {
|
|
||||||
bufferLen := len(buffer)
|
|
||||||
totalRead := 0
|
|
||||||
offset := 0
|
|
||||||
overflow := false
|
|
||||||
reset := false
|
|
||||||
for {
|
|
||||||
// We read into the buffer
|
|
||||||
n, err := reader.Read(buffer[offset:])
|
|
||||||
totalRead += n
|
|
||||||
offset += n
|
|
||||||
if !overflow && reset && n != 0 {
|
|
||||||
// In case we've started overwriting the beginning of the buffer
|
|
||||||
// We will return an error at Finish()
|
|
||||||
overflow = true
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return 0, errors.Wrap(err, "gopenpgp: couldn't read data from the encrypted reader")
|
|
||||||
}
|
|
||||||
if offset == bufferLen {
|
|
||||||
// Here we've reached the end of the buffer
|
|
||||||
// But we need to keep reading to not block the Process()
|
|
||||||
// So we reset the buffer
|
|
||||||
reset = true
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if overflow {
|
|
||||||
return 0, errors.New("gopenpgp: read more bytes that was allocated in the buffer")
|
|
||||||
}
|
|
||||||
return totalRead, nil
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// Package crypto provides a high-level API for common OpenPGP functionality.
|
|
||||||
package crypto
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
// GopenPGP is used as a "namespace" for many of the functions in this package.
|
|
||||||
// It is a struct that keeps track of time skew between server and client.
|
|
||||||
type GopenPGP struct {
|
|
||||||
latestServerTime int64
|
|
||||||
generationOffset int64
|
|
||||||
lock *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
var pgp = GopenPGP{
|
|
||||||
latestServerTime: 0,
|
|
||||||
generationOffset: 0,
|
|
||||||
lock: &sync.RWMutex{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// clone returns a clone of the byte slice. Internal function used to make sure
|
|
||||||
// we don't retain a reference to external data.
|
|
||||||
func clone(input []byte) []byte {
|
|
||||||
data := make([]byte, len(input))
|
|
||||||
copy(data, input)
|
|
||||||
return data
|
|
||||||
}
|
|
@ -1,489 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/armor"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
openpgp "github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
packet "github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Key contains a single private or public key.
|
|
||||||
type Key struct {
|
|
||||||
// PGP entities in this keyring.
|
|
||||||
entity *openpgp.Entity
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Create Key object
|
|
||||||
|
|
||||||
// NewKeyFromArmoredReader reads an armored data into a key.
|
|
||||||
func NewKeyFromArmoredReader(r io.Reader) (key *Key, err error) {
|
|
||||||
key = &Key{}
|
|
||||||
err = key.readFrom(r, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKeyFromReader reads binary data into a Key object.
|
|
||||||
func NewKeyFromReader(r io.Reader) (key *Key, err error) {
|
|
||||||
key = &Key{}
|
|
||||||
err = key.readFrom(r, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKey creates a new key from the first key in the unarmored binary data.
|
|
||||||
func NewKey(binKeys []byte) (key *Key, err error) {
|
|
||||||
return NewKeyFromReader(bytes.NewReader(clone(binKeys)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKeyFromArmored creates a new key from the first key in an armored string.
|
|
||||||
func NewKeyFromArmored(armored string) (key *Key, err error) {
|
|
||||||
return NewKeyFromArmoredReader(strings.NewReader(armored))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKeyFromEntity(entity *openpgp.Entity) (*Key, error) {
|
|
||||||
if entity == nil {
|
|
||||||
return nil, errors.New("gopenpgp: nil entity provided")
|
|
||||||
}
|
|
||||||
return &Key{entity: entity}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
|
|
||||||
func GenerateRSAKeyWithPrimes(
|
|
||||||
name, email string,
|
|
||||||
bits int,
|
|
||||||
primeone, primetwo, primethree, primefour []byte,
|
|
||||||
) (*Key, error) {
|
|
||||||
return generateKey(name, email, "rsa", bits, primeone, primetwo, primethree, primefour)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateKey generates a key of the given keyType ("rsa" or "x25519").
|
|
||||||
// If keyType is "rsa", bits is the RSA bitsize of the key.
|
|
||||||
// If keyType is "x25519" bits is unused.
|
|
||||||
func GenerateKey(name, email string, keyType string, bits int) (*Key, error) {
|
|
||||||
return generateKey(name, email, keyType, bits, nil, nil, nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Operate on key
|
|
||||||
|
|
||||||
// Copy creates a deep copy of the key.
|
|
||||||
func (key *Key) Copy() (*Key, error) {
|
|
||||||
serialized, err := key.Serialize()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewKey(serialized)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock locks a copy of the key.
|
|
||||||
func (key *Key) Lock(passphrase []byte) (*Key, error) {
|
|
||||||
unlocked, err := key.IsUnlocked()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !unlocked {
|
|
||||||
return nil, errors.New("gopenpgp: key is not unlocked")
|
|
||||||
}
|
|
||||||
|
|
||||||
lockedKey, err := key.Copy()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if passphrase == nil {
|
|
||||||
return lockedKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if lockedKey.entity.PrivateKey != nil && !lockedKey.entity.PrivateKey.Dummy() {
|
|
||||||
err = lockedKey.entity.PrivateKey.Encrypt(passphrase)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in locking key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sub := range lockedKey.entity.Subkeys {
|
|
||||||
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() {
|
|
||||||
if err := sub.PrivateKey.Encrypt(passphrase); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in locking sub key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locked, err := lockedKey.IsLocked()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !locked {
|
|
||||||
return nil, errors.New("gopenpgp: unable to lock key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return lockedKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock unlocks a copy of the key.
|
|
||||||
func (key *Key) Unlock(passphrase []byte) (*Key, error) {
|
|
||||||
isLocked, err := key.IsLocked()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isLocked {
|
|
||||||
if passphrase == nil {
|
|
||||||
return key.Copy()
|
|
||||||
}
|
|
||||||
return nil, errors.New("gopenpgp: key is not locked")
|
|
||||||
}
|
|
||||||
|
|
||||||
unlockedKey, err := key.Copy()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if unlockedKey.entity.PrivateKey != nil && !unlockedKey.entity.PrivateKey.Dummy() {
|
|
||||||
err = unlockedKey.entity.PrivateKey.Decrypt(passphrase)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in unlocking key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sub := range unlockedKey.entity.Subkeys {
|
|
||||||
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() {
|
|
||||||
if err := sub.PrivateKey.Decrypt(passphrase); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in unlocking sub key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isUnlocked, err := unlockedKey.IsUnlocked()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !isUnlocked {
|
|
||||||
return nil, errors.New("gopenpgp: unable to unlock key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return unlockedKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Export key
|
|
||||||
|
|
||||||
func (key *Key) Serialize() ([]byte, error) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if key.entity.PrivateKey == nil {
|
|
||||||
err = key.entity.Serialize(&buffer)
|
|
||||||
} else {
|
|
||||||
err = key.entity.SerializePrivateWithoutSigning(&buffer, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in serializing key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Armor returns the armored key as a string with default gopenpgp headers.
|
|
||||||
func (key *Key) Armor() (string, error) {
|
|
||||||
serialized, err := key.Serialize()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.IsPrivate() {
|
|
||||||
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
return armor.ArmorWithType(serialized, constants.PublicKeyHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArmorWithCustomHeaders returns the armored key as a string, with
|
|
||||||
// the given headers. Empty parameters are omitted from the headers.
|
|
||||||
func (key *Key) ArmorWithCustomHeaders(comment, version string) (string, error) {
|
|
||||||
serialized, err := key.Serialize()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return armor.ArmorWithTypeAndCustomHeaders(serialized, constants.PrivateKeyHeader, version, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArmoredPublicKey returns the armored public keys from this keyring.
|
|
||||||
func (key *Key) GetArmoredPublicKey() (s string, err error) {
|
|
||||||
serialized, err := key.GetPublicKey()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return armor.ArmorWithType(serialized, constants.PublicKeyHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArmoredPublicKeyWithCustomHeaders returns the armored public key as a string, with
|
|
||||||
// the given headers. Empty parameters are omitted from the headers.
|
|
||||||
func (key *Key) GetArmoredPublicKeyWithCustomHeaders(comment, version string) (string, error) {
|
|
||||||
serialized, err := key.GetPublicKey()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return armor.ArmorWithTypeAndCustomHeaders(serialized, constants.PublicKeyHeader, version, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPublicKey returns the unarmored public keys from this keyring.
|
|
||||||
func (key *Key) GetPublicKey() (b []byte, err error) {
|
|
||||||
var outBuf bytes.Buffer
|
|
||||||
if err = key.entity.Serialize(&outBuf); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in serializing public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return outBuf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Key object properties
|
|
||||||
|
|
||||||
// CanVerify returns true if any of the subkeys can be used for verification.
|
|
||||||
func (key *Key) CanVerify() bool {
|
|
||||||
_, canVerify := key.entity.SigningKey(getNow())
|
|
||||||
return canVerify
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanEncrypt returns true if any of the subkeys can be used for encryption.
|
|
||||||
func (key *Key) CanEncrypt() bool {
|
|
||||||
_, canEncrypt := key.entity.EncryptionKey(getNow())
|
|
||||||
return canEncrypt
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsExpired checks whether the key is expired.
|
|
||||||
func (key *Key) IsExpired() bool {
|
|
||||||
i := key.entity.PrimaryIdentity()
|
|
||||||
return key.entity.PrimaryKey.KeyExpired(i.SelfSignature, getNow()) || // primary key has expired
|
|
||||||
i.SelfSignature.SigExpired(getNow()) // user ID self-signature has expired
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRevoked checks whether the key or the primary identity has a valid revocation signature.
|
|
||||||
func (key *Key) IsRevoked() bool {
|
|
||||||
return key.entity.Revoked(getNow()) || key.entity.PrimaryIdentity().Revoked(getNow())
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrivate returns true if the key is private.
|
|
||||||
func (key *Key) IsPrivate() bool {
|
|
||||||
return key.entity.PrivateKey != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLocked checks if a private key is locked.
|
|
||||||
func (key *Key) IsLocked() (bool, error) {
|
|
||||||
if key.entity.PrivateKey == nil {
|
|
||||||
return true, errors.New("gopenpgp: a public key cannot be locked")
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedKeys := 0
|
|
||||||
|
|
||||||
for _, sub := range key.entity.Subkeys {
|
|
||||||
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted {
|
|
||||||
encryptedKeys++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.entity.PrivateKey.Encrypted {
|
|
||||||
encryptedKeys++
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptedKeys > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUnlocked checks if a private key is unlocked.
|
|
||||||
func (key *Key) IsUnlocked() (bool, error) {
|
|
||||||
if key.entity.PrivateKey == nil {
|
|
||||||
return true, errors.New("gopenpgp: a public key cannot be unlocked")
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedKeys := 0
|
|
||||||
|
|
||||||
for _, sub := range key.entity.Subkeys {
|
|
||||||
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted {
|
|
||||||
encryptedKeys++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.entity.PrivateKey.Encrypted {
|
|
||||||
encryptedKeys++
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptedKeys == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check verifies if the public keys match the private key parameters by
|
|
||||||
// signing and verifying.
|
|
||||||
// Deprecated: all keys are now checked on parsing.
|
|
||||||
func (key *Key) Check() (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
|
|
||||||
func (key *Key) PrintFingerprints() {
|
|
||||||
for _, subKey := range key.entity.Subkeys {
|
|
||||||
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
|
|
||||||
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("PrimaryKey:" + hex.EncodeToString(key.entity.PrimaryKey.Fingerprint))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHexKeyID returns the key ID, hex encoded as a string.
|
|
||||||
func (key *Key) GetHexKeyID() string {
|
|
||||||
return keyIDToHex(key.GetKeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeyID returns the key ID, encoded as 8-byte int.
|
|
||||||
func (key *Key) GetKeyID() uint64 {
|
|
||||||
return key.entity.PrimaryKey.KeyId
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFingerprint gets the fingerprint from the key.
|
|
||||||
func (key *Key) GetFingerprint() string {
|
|
||||||
return hex.EncodeToString(key.entity.PrimaryKey.Fingerprint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSHA256Fingerprints computes the SHA256 fingerprints of the key and subkeys.
|
|
||||||
func (key *Key) GetSHA256Fingerprints() (fingerprints []string) {
|
|
||||||
fingerprints = append(fingerprints, hex.EncodeToString(getSHA256FingerprintBytes(key.entity.PrimaryKey)))
|
|
||||||
for _, sub := range key.entity.Subkeys {
|
|
||||||
fingerprints = append(fingerprints, hex.EncodeToString(getSHA256FingerprintBytes(sub.PublicKey)))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEntity gets x/crypto Entity object.
|
|
||||||
func (key *Key) GetEntity() *openpgp.Entity {
|
|
||||||
return key.entity
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToPublic returns the corresponding public key of the given private key.
|
|
||||||
func (key *Key) ToPublic() (publicKey *Key, err error) {
|
|
||||||
if !key.IsPrivate() {
|
|
||||||
return nil, errors.New("gopenpgp: key is already public")
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKey, err = key.Copy()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKey.ClearPrivateParams()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Internal methods
|
|
||||||
|
|
||||||
// getSHA256FingerprintBytes computes the SHA256 fingerprint of a public key
|
|
||||||
// object.
|
|
||||||
func getSHA256FingerprintBytes(pk *packet.PublicKey) []byte {
|
|
||||||
fingerPrint := sha256.New()
|
|
||||||
|
|
||||||
// Hashing can't return an error, and has already been done when parsing the key,
|
|
||||||
// hence the error is nil
|
|
||||||
_ = pk.SerializeForHash(fingerPrint)
|
|
||||||
return fingerPrint.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
|
|
||||||
func (key *Key) readFrom(r io.Reader, armored bool) error {
|
|
||||||
var err error
|
|
||||||
var entities openpgp.EntityList
|
|
||||||
if armored {
|
|
||||||
entities, err = openpgp.ReadArmoredKeyRing(r)
|
|
||||||
} else {
|
|
||||||
entities, err = openpgp.ReadKeyRing(r)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "gopenpgp: error in reading key ring")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(entities) > 1 {
|
|
||||||
return errors.New("gopenpgp: the key contains too many entities")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(entities) == 0 {
|
|
||||||
return errors.New("gopenpgp: the key does not contain any entity")
|
|
||||||
}
|
|
||||||
|
|
||||||
key.entity = entities[0]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateKey(
|
|
||||||
name, email string,
|
|
||||||
keyType string,
|
|
||||||
bits int,
|
|
||||||
prime1, prime2, prime3, prime4 []byte,
|
|
||||||
) (*Key, error) {
|
|
||||||
if len(email) == 0 && len(name) == 0 {
|
|
||||||
return nil, errors.New("gopenpgp: neither name nor email set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
comments := ""
|
|
||||||
|
|
||||||
cfg := &packet.Config{
|
|
||||||
Algorithm: packet.PubKeyAlgoRSA,
|
|
||||||
RSABits: bits,
|
|
||||||
Time: getKeyGenerationTimeGenerator(),
|
|
||||||
DefaultHash: crypto.SHA256,
|
|
||||||
DefaultCipher: packet.CipherAES256,
|
|
||||||
DefaultCompressionAlgo: packet.CompressionZLIB,
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyType == "x25519" {
|
|
||||||
cfg.Algorithm = packet.PubKeyAlgoEdDSA
|
|
||||||
}
|
|
||||||
|
|
||||||
if prime1 != nil && prime2 != nil && prime3 != nil && prime4 != nil {
|
|
||||||
var bigPrimes [4]*big.Int
|
|
||||||
bigPrimes[0] = new(big.Int)
|
|
||||||
bigPrimes[0].SetBytes(prime1)
|
|
||||||
bigPrimes[1] = new(big.Int)
|
|
||||||
bigPrimes[1].SetBytes(prime2)
|
|
||||||
bigPrimes[2] = new(big.Int)
|
|
||||||
bigPrimes[2].SetBytes(prime3)
|
|
||||||
bigPrimes[3] = new(big.Int)
|
|
||||||
bigPrimes[3].SetBytes(prime4)
|
|
||||||
|
|
||||||
cfg.RSAPrimes = bigPrimes[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopengpp: error in encoding new entity")
|
|
||||||
}
|
|
||||||
|
|
||||||
if newEntity.PrivateKey == nil {
|
|
||||||
return nil, errors.New("gopenpgp: error in generating private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewKeyFromEntity(newEntity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyIDToHex casts a keyID to hex with the correct padding.
|
|
||||||
func keyIDToHex(keyID uint64) string {
|
|
||||||
return fmt.Sprintf("%016v", strconv.FormatUint(keyID, 16))
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/dsa" //nolint:staticcheck
|
|
||||||
"crypto/rsa"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (sk *SessionKey) Clear() (ok bool) {
|
|
||||||
clearMem(sk.Key)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *Key) ClearPrivateParams() (ok bool) {
|
|
||||||
num := key.clearPrivateWithSubkeys()
|
|
||||||
key.entity.PrivateKey = nil
|
|
||||||
|
|
||||||
for k := range key.entity.Subkeys {
|
|
||||||
key.entity.Subkeys[k].PrivateKey = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return num > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *Key) clearPrivateWithSubkeys() (num int) {
|
|
||||||
num = 0
|
|
||||||
if key.entity.PrivateKey != nil {
|
|
||||||
err := clearPrivateKey(key.entity.PrivateKey.PrivateKey)
|
|
||||||
if err == nil {
|
|
||||||
num++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k := range key.entity.Subkeys {
|
|
||||||
if key.entity.Subkeys[k].PrivateKey != nil {
|
|
||||||
err := clearPrivateKey(key.entity.Subkeys[k].PrivateKey.PrivateKey)
|
|
||||||
if err == nil {
|
|
||||||
num++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearPrivateKey(privateKey interface{}) error {
|
|
||||||
switch priv := privateKey.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return clearRSAPrivateKey(priv)
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
return clearDSAPrivateKey(priv)
|
|
||||||
case *elgamal.PrivateKey:
|
|
||||||
return clearElGamalPrivateKey(priv)
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
return clearECDSAPrivateKey(priv)
|
|
||||||
case *eddsa.PrivateKey:
|
|
||||||
return clearEdDSAPrivateKey(priv)
|
|
||||||
case *ecdh.PrivateKey:
|
|
||||||
return clearECDHPrivateKey(priv)
|
|
||||||
default:
|
|
||||||
return errors.New("gopenpgp: unknown private key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearBigInt(n *big.Int) {
|
|
||||||
w := n.Bits()
|
|
||||||
for k := range w {
|
|
||||||
w[k] = 0x00
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearMem(w []byte) {
|
|
||||||
for k := range w {
|
|
||||||
w[k] = 0x00
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearRSAPrivateKey(rsaPriv *rsa.PrivateKey) error {
|
|
||||||
clearBigInt(rsaPriv.D)
|
|
||||||
for idx := range rsaPriv.Primes {
|
|
||||||
clearBigInt(rsaPriv.Primes[idx])
|
|
||||||
}
|
|
||||||
clearBigInt(rsaPriv.Precomputed.Qinv)
|
|
||||||
clearBigInt(rsaPriv.Precomputed.Dp)
|
|
||||||
clearBigInt(rsaPriv.Precomputed.Dq)
|
|
||||||
|
|
||||||
for idx := range rsaPriv.Precomputed.CRTValues {
|
|
||||||
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].Exp)
|
|
||||||
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].Coeff)
|
|
||||||
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].R)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearDSAPrivateKey(priv *dsa.PrivateKey) error {
|
|
||||||
clearBigInt(priv.X)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearElGamalPrivateKey(priv *elgamal.PrivateKey) error {
|
|
||||||
clearBigInt(priv.X)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearECDSAPrivateKey(priv *ecdsa.PrivateKey) error {
|
|
||||||
clearBigInt(priv.D)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearEdDSAPrivateKey(priv *eddsa.PrivateKey) error {
|
|
||||||
clearMem(priv.D)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearECDHPrivateKey(priv *ecdh.PrivateKey) error {
|
|
||||||
clearMem(priv.D)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,250 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeyRing contains multiple private and public keys.
|
|
||||||
type KeyRing struct {
|
|
||||||
// PGP entities in this keyring.
|
|
||||||
entities openpgp.EntityList
|
|
||||||
|
|
||||||
// FirstKeyID as obtained from API to match salt
|
|
||||||
FirstKeyID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identity contains the name and the email of a key holder.
|
|
||||||
type Identity struct {
|
|
||||||
Name string
|
|
||||||
Email string
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- New keyrings
|
|
||||||
|
|
||||||
// NewKeyRing creates a new KeyRing, empty if key is nil.
|
|
||||||
func NewKeyRing(key *Key) (*KeyRing, error) {
|
|
||||||
keyRing := &KeyRing{}
|
|
||||||
var err error
|
|
||||||
if key != nil {
|
|
||||||
err = keyRing.AddKey(key)
|
|
||||||
}
|
|
||||||
return keyRing, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddKey adds the given key to the keyring.
|
|
||||||
func (keyRing *KeyRing) AddKey(key *Key) error {
|
|
||||||
if key.IsPrivate() {
|
|
||||||
unlocked, err := key.IsUnlocked()
|
|
||||||
if err != nil || !unlocked {
|
|
||||||
return errors.New("gopenpgp: unable to add locked key to a keyring")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyRing.appendKey(key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Extract keys from keyring
|
|
||||||
|
|
||||||
// GetKeys returns openpgp keys contained in this KeyRing.
|
|
||||||
func (keyRing *KeyRing) GetKeys() []*Key {
|
|
||||||
keys := make([]*Key, keyRing.CountEntities())
|
|
||||||
for i, entity := range keyRing.entities {
|
|
||||||
keys[i] = &Key{entity}
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKey returns the n-th openpgp key contained in this KeyRing.
|
|
||||||
func (keyRing *KeyRing) GetKey(n int) (*Key, error) {
|
|
||||||
if n >= keyRing.CountEntities() {
|
|
||||||
return nil, errors.New("gopenpgp: out of bound when fetching key")
|
|
||||||
}
|
|
||||||
return &Key{keyRing.entities[n]}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSigningEntity returns first private unlocked signing entity from keyring.
|
|
||||||
func (keyRing *KeyRing) getSigningEntity() (*openpgp.Entity, error) {
|
|
||||||
var signEntity *openpgp.Entity
|
|
||||||
|
|
||||||
for _, e := range keyRing.entities {
|
|
||||||
// Entity.PrivateKey must be a signing key
|
|
||||||
if e.PrivateKey != nil {
|
|
||||||
if !e.PrivateKey.Encrypted {
|
|
||||||
signEntity = e
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if signEntity == nil {
|
|
||||||
return nil, errors.New("gopenpgp: cannot sign message, unable to unlock signer key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return signEntity, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Extract info from key
|
|
||||||
|
|
||||||
// CountEntities returns the number of entities in the keyring.
|
|
||||||
func (keyRing *KeyRing) CountEntities() int {
|
|
||||||
return len(keyRing.entities)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountDecryptionEntities returns the number of entities in the keyring.
|
|
||||||
func (keyRing *KeyRing) CountDecryptionEntities() int {
|
|
||||||
return len(keyRing.entities.DecryptionKeys())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIdentities returns the list of identities associated with this key ring.
|
|
||||||
func (keyRing *KeyRing) GetIdentities() []*Identity {
|
|
||||||
var identities []*Identity
|
|
||||||
for _, e := range keyRing.entities {
|
|
||||||
for _, id := range e.Identities {
|
|
||||||
identities = append(identities, &Identity{
|
|
||||||
Name: id.UserId.Name,
|
|
||||||
Email: id.UserId.Email,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return identities
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanVerify returns true if any of the keys in the keyring can be used for verification.
|
|
||||||
func (keyRing *KeyRing) CanVerify() bool {
|
|
||||||
keys := keyRing.GetKeys()
|
|
||||||
for _, key := range keys {
|
|
||||||
if key.CanVerify() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanEncrypt returns true if any of the keys in the keyring can be used for encryption.
|
|
||||||
func (keyRing *KeyRing) CanEncrypt() bool {
|
|
||||||
keys := keyRing.GetKeys()
|
|
||||||
for _, key := range keys {
|
|
||||||
if key.CanEncrypt() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeyIDs returns array of IDs of keys in this KeyRing.
|
|
||||||
func (keyRing *KeyRing) GetKeyIDs() []uint64 {
|
|
||||||
var res = make([]uint64, len(keyRing.entities))
|
|
||||||
for id, e := range keyRing.entities {
|
|
||||||
res[id] = e.PrimaryKey.KeyId
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Filter keyrings
|
|
||||||
|
|
||||||
// FilterExpiredKeys takes a given KeyRing list and it returns only those
|
|
||||||
// KeyRings which contain at least, one unexpired Key. It returns only unexpired
|
|
||||||
// parts of these KeyRings.
|
|
||||||
func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err error) {
|
|
||||||
now := time.Now()
|
|
||||||
hasExpiredEntity := false //nolint:ifshort
|
|
||||||
filteredKeys = make([]*KeyRing, 0)
|
|
||||||
|
|
||||||
for _, contactKeyRing := range contactKeys {
|
|
||||||
keyRingHasUnexpiredEntity := false
|
|
||||||
keyRingHasTotallyExpiredEntity := false
|
|
||||||
for _, entity := range contactKeyRing.entities {
|
|
||||||
hasExpired := false
|
|
||||||
hasUnexpired := false
|
|
||||||
for _, subkey := range entity.Subkeys {
|
|
||||||
if subkey.PublicKey.KeyExpired(subkey.Sig, now) {
|
|
||||||
hasExpired = true
|
|
||||||
} else {
|
|
||||||
hasUnexpired = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasExpired && !hasUnexpired {
|
|
||||||
keyRingHasTotallyExpiredEntity = true
|
|
||||||
} else if hasUnexpired {
|
|
||||||
keyRingHasUnexpiredEntity = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if keyRingHasUnexpiredEntity {
|
|
||||||
keyRingCopy, err := contactKeyRing.Copy()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredKeys = append(filteredKeys, keyRingCopy)
|
|
||||||
} else if keyRingHasTotallyExpiredEntity {
|
|
||||||
hasExpiredEntity = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filteredKeys) == 0 && hasExpiredEntity {
|
|
||||||
return filteredKeys, errors.New("gopenpgp: all contacts keys are expired")
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredKeys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FirstKey returns a KeyRing with only the first key of the original one.
|
|
||||||
func (keyRing *KeyRing) FirstKey() (*KeyRing, error) {
|
|
||||||
if len(keyRing.entities) == 0 {
|
|
||||||
return nil, errors.New("gopenpgp: No key available in this keyring")
|
|
||||||
}
|
|
||||||
newKeyRing := &KeyRing{}
|
|
||||||
newKeyRing.entities = keyRing.entities[:1]
|
|
||||||
|
|
||||||
return newKeyRing.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a deep copy of the keyring.
|
|
||||||
func (keyRing *KeyRing) Copy() (*KeyRing, error) {
|
|
||||||
newKeyRing := &KeyRing{}
|
|
||||||
|
|
||||||
entities := make([]*openpgp.Entity, len(keyRing.entities))
|
|
||||||
for id, entity := range keyRing.entities {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if entity.PrivateKey == nil {
|
|
||||||
err = entity.Serialize(&buffer)
|
|
||||||
} else {
|
|
||||||
err = entity.SerializePrivateWithoutSigning(&buffer, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: unable to copy key: error in serializing entity")
|
|
||||||
}
|
|
||||||
|
|
||||||
bt := buffer.Bytes()
|
|
||||||
entities[id], err = openpgp.ReadEntity(packet.NewReader(bytes.NewReader(bt)))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: unable to copy key: error in reading entity")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newKeyRing.entities = entities
|
|
||||||
newKeyRing.FirstKeyID = keyRing.FirstKeyID
|
|
||||||
|
|
||||||
return newKeyRing, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (keyRing *KeyRing) ClearPrivateParams() {
|
|
||||||
for _, key := range keyRing.GetKeys() {
|
|
||||||
key.ClearPrivateParams()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// INTERNAL FUNCTIONS
|
|
||||||
|
|
||||||
// appendKey appends a key to the keyring.
|
|
||||||
func (keyRing *KeyRing) appendKey(key *Key) {
|
|
||||||
keyRing.entities = append(keyRing.entities, key.entity)
|
|
||||||
}
|
|
@ -1,355 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encrypt encrypts a PlainMessage, outputs a PGPMessage.
|
|
||||||
// If an unlocked private key is also provided it will also sign the message.
|
|
||||||
// * message : The plaintext input as a PlainMessage.
|
|
||||||
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
|
|
||||||
func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
|
|
||||||
return asymmetricEncrypt(message, keyRing, privateKey, false, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptWithContext encrypts a PlainMessage, outputs a PGPMessage.
|
|
||||||
// If an unlocked private key is also provided it will also sign the message.
|
|
||||||
// * message : The plaintext input as a PlainMessage.
|
|
||||||
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
|
|
||||||
// * signingContext : (optional) the context for the signature.
|
|
||||||
func (keyRing *KeyRing) EncryptWithContext(message *PlainMessage, privateKey *KeyRing, signingContext *SigningContext) (*PGPMessage, error) {
|
|
||||||
return asymmetricEncrypt(message, keyRing, privateKey, false, signingContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptWithCompression encrypts with compression support a PlainMessage to PGPMessage using public/private keys.
|
|
||||||
// * message : The plain data as a PlainMessage.
|
|
||||||
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
|
|
||||||
// * output : The encrypted data as PGPMessage.
|
|
||||||
func (keyRing *KeyRing) EncryptWithCompression(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
|
|
||||||
return asymmetricEncrypt(message, keyRing, privateKey, true, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptWithContextAndCompression encrypts with compression support a PlainMessage to PGPMessage using public/private keys.
|
|
||||||
// * message : The plain data as a PlainMessage.
|
|
||||||
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
|
|
||||||
// * signingContext : (optional) the context for the signature.
|
|
||||||
// * output : The encrypted data as PGPMessage.
|
|
||||||
func (keyRing *KeyRing) EncryptWithContextAndCompression(message *PlainMessage, privateKey *KeyRing, signingContext *SigningContext) (*PGPMessage, error) {
|
|
||||||
return asymmetricEncrypt(message, keyRing, privateKey, true, signingContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt decrypts encrypted string using pgp keys, returning a PlainMessage
|
|
||||||
// * message : The encrypted input as a PGPMessage
|
|
||||||
// * verifyKey : Public key for signature verification (optional)
|
|
||||||
// * verifyTime : Time at verification (necessary only if verifyKey is not nil)
|
|
||||||
// * verificationContext : (optional) the context for the signature verification.
|
|
||||||
//
|
|
||||||
// When verifyKey is not provided, then verifyTime should be zero, and
|
|
||||||
// signature verification will be ignored.
|
|
||||||
func (keyRing *KeyRing) Decrypt(
|
|
||||||
message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
|
|
||||||
) (*PlainMessage, error) {
|
|
||||||
return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptWithContext decrypts encrypted string using pgp keys, returning a PlainMessage
|
|
||||||
// * message : The encrypted input as a PGPMessage
|
|
||||||
// * verifyKey : Public key for signature verification (optional)
|
|
||||||
// * verifyTime : Time at verification (necessary only if verifyKey is not nil)
|
|
||||||
// * verificationContext : (optional) the context for the signature verification.
|
|
||||||
//
|
|
||||||
// When verifyKey is not provided, then verifyTime should be zero, and
|
|
||||||
// signature verification will be ignored.
|
|
||||||
func (keyRing *KeyRing) DecryptWithContext(
|
|
||||||
message *PGPMessage,
|
|
||||||
verifyKey *KeyRing,
|
|
||||||
verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) (*PlainMessage, error) {
|
|
||||||
return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime, verificationContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignDetached generates and returns a PGPSignature for a given PlainMessage.
|
|
||||||
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
|
|
||||||
return keyRing.SignDetachedWithContext(message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignDetachedWithContext generates and returns a PGPSignature for a given PlainMessage.
|
|
||||||
// If a context is provided, it is added to the signature as notation data
|
|
||||||
// with the name set in `constants.SignatureContextName`.
|
|
||||||
func (keyRing *KeyRing) SignDetachedWithContext(message *PlainMessage, context *SigningContext) (*PGPSignature, error) {
|
|
||||||
return signMessageDetached(
|
|
||||||
keyRing,
|
|
||||||
message.NewReader(),
|
|
||||||
message.IsBinary(),
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetached verifies a PlainMessage with a detached PGPSignature
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) error {
|
|
||||||
_, err := verifySignature(
|
|
||||||
keyRing.entities,
|
|
||||||
message.NewReader(),
|
|
||||||
signature.GetBinary(),
|
|
||||||
verifyTime,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetachedWithContext verifies a PlainMessage with a detached PGPSignature
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
// If a context is provided, it verifies that the signature is valid in the given context, using
|
|
||||||
// the signature notation with name the name set in `constants.SignatureContextName`.
|
|
||||||
func (keyRing *KeyRing) VerifyDetachedWithContext(message *PlainMessage, signature *PGPSignature, verifyTime int64, verificationContext *VerificationContext) error {
|
|
||||||
_, err := verifySignature(
|
|
||||||
keyRing.entities,
|
|
||||||
message.NewReader(),
|
|
||||||
signature.GetBinary(),
|
|
||||||
verifyTime,
|
|
||||||
verificationContext,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignDetachedEncrypted generates and returns a PGPMessage
|
|
||||||
// containing an encrypted detached signature for a given PlainMessage.
|
|
||||||
func (keyRing *KeyRing) SignDetachedEncrypted(message *PlainMessage, encryptionKeyRing *KeyRing) (encryptedSignature *PGPMessage, err error) {
|
|
||||||
if encryptionKeyRing == nil {
|
|
||||||
return nil, errors.New("gopenpgp: no encryption key ring provided")
|
|
||||||
}
|
|
||||||
signature, err := keyRing.SignDetached(message)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
plainMessage := NewPlainMessage(signature.GetBinary())
|
|
||||||
encryptedSignature, err = encryptionKeyRing.Encrypt(plainMessage, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetachedEncrypted verifies a PlainMessage
|
|
||||||
// with a PGPMessage containing an encrypted detached signature
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
func (keyRing *KeyRing) VerifyDetachedEncrypted(message *PlainMessage, encryptedSignature *PGPMessage, decryptionKeyRing *KeyRing, verifyTime int64) error {
|
|
||||||
if decryptionKeyRing == nil {
|
|
||||||
return errors.New("gopenpgp: no decryption key ring provided")
|
|
||||||
}
|
|
||||||
plainMessage, err := decryptionKeyRing.Decrypt(encryptedSignature, nil, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
signature := NewPGPSignature(plainMessage.GetBinary())
|
|
||||||
return keyRing.VerifyDetached(message, signature, verifyTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVerifiedSignatureTimestamp verifies a PlainMessage with a detached PGPSignature
|
|
||||||
// returns the creation time of the signature if it succeeds
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
func (keyRing *KeyRing) GetVerifiedSignatureTimestamp(message *PlainMessage, signature *PGPSignature, verifyTime int64) (int64, error) {
|
|
||||||
sigPacket, err := verifySignature(
|
|
||||||
keyRing.entities,
|
|
||||||
message.NewReader(),
|
|
||||||
signature.GetBinary(),
|
|
||||||
verifyTime,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return sigPacket.CreationTime.Unix(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVerifiedSignatureTimestampWithContext verifies a PlainMessage with a detached PGPSignature
|
|
||||||
// returns the creation time of the signature if it succeeds
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
// If a context is provided, it verifies that the signature is valid in the given context, using
|
|
||||||
// the signature notation with name the name set in `constants.SignatureContextName`.
|
|
||||||
func (keyRing *KeyRing) GetVerifiedSignatureTimestampWithContext(
|
|
||||||
message *PlainMessage,
|
|
||||||
signature *PGPSignature,
|
|
||||||
verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) (int64, error) {
|
|
||||||
sigPacket, err := verifySignature(
|
|
||||||
keyRing.entities,
|
|
||||||
message.NewReader(),
|
|
||||||
signature.GetBinary(),
|
|
||||||
verifyTime,
|
|
||||||
verificationContext,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return sigPacket.CreationTime.Unix(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------ INTERNAL FUNCTIONS -------
|
|
||||||
|
|
||||||
// Core for encryption+signature (non-streaming) functions.
|
|
||||||
func asymmetricEncrypt(
|
|
||||||
plainMessage *PlainMessage,
|
|
||||||
publicKey, privateKey *KeyRing,
|
|
||||||
compress bool,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (*PGPMessage, error) {
|
|
||||||
var outBuf bytes.Buffer
|
|
||||||
var encryptWriter io.WriteCloser
|
|
||||||
var err error
|
|
||||||
|
|
||||||
hints := &openpgp.FileHints{
|
|
||||||
IsBinary: plainMessage.IsBinary(),
|
|
||||||
FileName: plainMessage.Filename,
|
|
||||||
ModTime: plainMessage.getFormattedTime(),
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptWriter, err = asymmetricEncryptStream(hints, &outBuf, &outBuf, publicKey, privateKey, compress, signingContext)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = encryptWriter.Write(plainMessage.GetBinary())
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in writing to message")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = encryptWriter.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in closing message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PGPMessage{outBuf.Bytes()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core for encryption+signature (all) functions.
|
|
||||||
func asymmetricEncryptStream(
|
|
||||||
hints *openpgp.FileHints,
|
|
||||||
keyPacketWriter io.Writer,
|
|
||||||
dataPacketWriter io.Writer,
|
|
||||||
publicKey, privateKey *KeyRing,
|
|
||||||
compress bool,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (encryptWriter io.WriteCloser, err error) {
|
|
||||||
config := &packet.Config{
|
|
||||||
DefaultCipher: packet.CipherAES256,
|
|
||||||
Time: getTimeGenerator(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if compress {
|
|
||||||
config.DefaultCompressionAlgo = constants.DefaultCompression
|
|
||||||
config.CompressionConfig = &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}
|
|
||||||
}
|
|
||||||
|
|
||||||
if signingContext != nil {
|
|
||||||
config.SignatureNotations = append(config.SignatureNotations, signingContext.getNotation())
|
|
||||||
}
|
|
||||||
|
|
||||||
var signEntity *openpgp.Entity
|
|
||||||
if privateKey != nil && len(privateKey.entities) > 0 {
|
|
||||||
var err error
|
|
||||||
signEntity, err = privateKey.getSigningEntity()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hints.IsBinary {
|
|
||||||
encryptWriter, err = openpgp.EncryptSplit(keyPacketWriter, dataPacketWriter, publicKey.entities, signEntity, hints, config)
|
|
||||||
} else {
|
|
||||||
encryptWriter, err = openpgp.EncryptTextSplit(keyPacketWriter, dataPacketWriter, publicKey.entities, signEntity, hints, config)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in encrypting asymmetrically")
|
|
||||||
}
|
|
||||||
return encryptWriter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core for decryption+verification (non streaming) functions.
|
|
||||||
func asymmetricDecrypt(
|
|
||||||
encryptedIO io.Reader,
|
|
||||||
privateKey *KeyRing,
|
|
||||||
verifyKey *KeyRing,
|
|
||||||
verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) (message *PlainMessage, err error) {
|
|
||||||
messageDetails, err := asymmetricDecryptStream(
|
|
||||||
encryptedIO,
|
|
||||||
privateKey,
|
|
||||||
verifyKey,
|
|
||||||
verifyTime,
|
|
||||||
verificationContext,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(messageDetails.UnverifiedBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in reading message body")
|
|
||||||
}
|
|
||||||
|
|
||||||
if verifyKey != nil {
|
|
||||||
processSignatureExpiration(messageDetails, verifyTime)
|
|
||||||
err = verifyDetailsSignature(messageDetails, verifyKey, verificationContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PlainMessage{
|
|
||||||
Data: body,
|
|
||||||
TextType: !messageDetails.LiteralData.IsBinary,
|
|
||||||
Filename: messageDetails.LiteralData.FileName,
|
|
||||||
Time: messageDetails.LiteralData.Time,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core for decryption+verification (all) functions.
|
|
||||||
func asymmetricDecryptStream(
|
|
||||||
encryptedIO io.Reader,
|
|
||||||
privateKey *KeyRing,
|
|
||||||
verifyKey *KeyRing,
|
|
||||||
verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) (messageDetails *openpgp.MessageDetails, err error) {
|
|
||||||
privKeyEntries := privateKey.entities
|
|
||||||
var additionalEntries openpgp.EntityList
|
|
||||||
|
|
||||||
if verifyKey != nil {
|
|
||||||
additionalEntries = verifyKey.entities
|
|
||||||
}
|
|
||||||
|
|
||||||
if additionalEntries != nil {
|
|
||||||
privKeyEntries = append(privKeyEntries, additionalEntries...)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &packet.Config{
|
|
||||||
Time: func() time.Time {
|
|
||||||
if verifyTime == 0 {
|
|
||||||
/*
|
|
||||||
We default to current time while decrypting and verifying
|
|
||||||
but the caller will remove signature expiration errors later on.
|
|
||||||
See processSignatureExpiration().
|
|
||||||
*/
|
|
||||||
return getNow()
|
|
||||||
}
|
|
||||||
return time.Unix(verifyTime, 0)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if verificationContext != nil {
|
|
||||||
config.KnownNotations = map[string]bool{constants.SignatureContextName: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
messageDetails, err = openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in reading message")
|
|
||||||
}
|
|
||||||
return messageDetails, err
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DecryptSessionKey returns the decrypted session key from one or multiple binary encrypted session key packets.
|
|
||||||
func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SessionKey, error) {
|
|
||||||
var p packet.Packet
|
|
||||||
var ek *packet.EncryptedKey
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var hasPacket = false
|
|
||||||
var decryptErr error
|
|
||||||
|
|
||||||
keyReader := bytes.NewReader(keyPacket)
|
|
||||||
packets := packet.NewReader(keyReader)
|
|
||||||
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
if p, err = packets.Next(); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p := p.(type) {
|
|
||||||
case *packet.EncryptedKey:
|
|
||||||
hasPacket = true
|
|
||||||
ek = p
|
|
||||||
|
|
||||||
for _, key := range keyRing.entities.DecryptionKeys() {
|
|
||||||
priv := key.PrivateKey
|
|
||||||
if priv.Encrypted {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *packet.SymmetricallyEncrypted,
|
|
||||||
*packet.AEADEncrypted,
|
|
||||||
*packet.Compressed,
|
|
||||||
*packet.LiteralData:
|
|
||||||
break Loop
|
|
||||||
|
|
||||||
default:
|
|
||||||
continue Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasPacket {
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: couldn't find a session key packet")
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("gopenpgp: couldn't find a session key packet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if decryptErr != nil {
|
|
||||||
return nil, errors.Wrap(decryptErr, "gopenpgp: error in decrypting")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ek == nil || ek.Key == nil {
|
|
||||||
return nil, errors.New("gopenpgp: unable to decrypt session key: no valid decryption key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return newSessionKeyFromEncrypted(ek)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSessionKey encrypts the session key with the unarmored
|
|
||||||
// publicKey and returns a binary public-key encrypted session key packet.
|
|
||||||
func (keyRing *KeyRing) EncryptSessionKey(sk *SessionKey) ([]byte, error) {
|
|
||||||
outbuf := &bytes.Buffer{}
|
|
||||||
cf, err := sk.GetCipherFunc()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key")
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKeys := make([]*packet.PublicKey, 0, len(keyRing.entities))
|
|
||||||
for _, e := range keyRing.entities {
|
|
||||||
encryptionKey, ok := e.EncryptionKey(getNow())
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("gopenpgp: encryption key is unavailable for key id " + strconv.FormatUint(e.PrimaryKey.KeyId, 16))
|
|
||||||
}
|
|
||||||
pubKeys = append(pubKeys, encryptionKey.PublicKey)
|
|
||||||
}
|
|
||||||
if len(pubKeys) == 0 {
|
|
||||||
return nil, errors.New("cannot set key: no public key available")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pub := range pubKeys {
|
|
||||||
if err := packet.SerializeEncryptedKey(outbuf, pub, cf, sk.Key, nil); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: cannot set key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outbuf.Bytes(), nil
|
|
||||||
}
|
|
@ -1,539 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Reader interface {
|
|
||||||
Read(b []byte) (n int, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Writer interface {
|
|
||||||
Write(b []byte) (n int, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriteCloser interface {
|
|
||||||
Write(b []byte) (n int, err error)
|
|
||||||
Close() (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PlainMessageMetadata struct {
|
|
||||||
IsBinary bool
|
|
||||||
Filename string
|
|
||||||
ModTime int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPlainMessageMetadata(isBinary bool, filename string, modTime int64) *PlainMessageMetadata {
|
|
||||||
return &PlainMessageMetadata{IsBinary: isBinary, Filename: filename, ModTime: modTime}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptStream is used to encrypt data as a Writer.
|
|
||||||
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptStream(
|
|
||||||
pgpMessageWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
) (plainMessageWriter WriteCloser, err error) {
|
|
||||||
return encryptStream(
|
|
||||||
keyRing,
|
|
||||||
pgpMessageWriter,
|
|
||||||
pgpMessageWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
false,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptStreamWithContext is used to encrypt data as a Writer.
|
|
||||||
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
// * signingContext : (optional) a context for the embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptStreamWithContext(
|
|
||||||
pgpMessageWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (plainMessageWriter WriteCloser, err error) {
|
|
||||||
return encryptStream(
|
|
||||||
keyRing,
|
|
||||||
pgpMessageWriter,
|
|
||||||
pgpMessageWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
false,
|
|
||||||
signingContext,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptStreamWithCompression is used to encrypt data as a Writer.
|
|
||||||
// The plaintext data is compressed before being encrypted.
|
|
||||||
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptStreamWithCompression(
|
|
||||||
pgpMessageWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
) (plainMessageWriter WriteCloser, err error) {
|
|
||||||
return encryptStream(
|
|
||||||
keyRing,
|
|
||||||
pgpMessageWriter,
|
|
||||||
pgpMessageWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
true,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptStreamWithContextAndCompression is used to encrypt data as a Writer.
|
|
||||||
// The plaintext data is compressed before being encrypted.
|
|
||||||
// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
// * signingContext : (optional) a context for the embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptStreamWithContextAndCompression(
|
|
||||||
pgpMessageWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (plainMessageWriter WriteCloser, err error) {
|
|
||||||
return encryptStream(
|
|
||||||
keyRing,
|
|
||||||
pgpMessageWriter,
|
|
||||||
pgpMessageWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
true,
|
|
||||||
signingContext,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encryptStream(
|
|
||||||
encryptionKeyRing *KeyRing,
|
|
||||||
keyPacketWriter Writer,
|
|
||||||
dataPacketWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
compress bool,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (plainMessageWriter WriteCloser, err error) {
|
|
||||||
if plainMessageMetadata == nil {
|
|
||||||
// Use sensible default metadata
|
|
||||||
plainMessageMetadata = &PlainMessageMetadata{
|
|
||||||
IsBinary: true,
|
|
||||||
Filename: "",
|
|
||||||
ModTime: GetUnixTime(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hints := &openpgp.FileHints{
|
|
||||||
FileName: plainMessageMetadata.Filename,
|
|
||||||
IsBinary: plainMessageMetadata.IsBinary,
|
|
||||||
ModTime: time.Unix(plainMessageMetadata.ModTime, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
plainMessageWriter, err = asymmetricEncryptStream(hints, keyPacketWriter, dataPacketWriter, encryptionKeyRing, signKeyRing, compress, signingContext)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return plainMessageWriter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSplitResult is used to wrap the encryption writecloser while storing the key packet.
|
|
||||||
type EncryptSplitResult struct {
|
|
||||||
isClosed bool
|
|
||||||
keyPacketBuf *bytes.Buffer
|
|
||||||
keyPacket []byte
|
|
||||||
plainMessageWriter WriteCloser // The writer to writer plaintext data in.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (res *EncryptSplitResult) Write(b []byte) (n int, err error) {
|
|
||||||
return res.plainMessageWriter.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (res *EncryptSplitResult) Close() (err error) {
|
|
||||||
err = res.plainMessageWriter.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.isClosed = true
|
|
||||||
res.keyPacket = res.keyPacketBuf.Bytes()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeyPacket returns the Public-Key Encrypted Session Key Packets (https://datatracker.ietf.org/doc/html/rfc4880#section-5.1).
|
|
||||||
// This can be retrieved only after the message has been fully written and the writer is closed.
|
|
||||||
func (res *EncryptSplitResult) GetKeyPacket() (keyPacket []byte, err error) {
|
|
||||||
if !res.isClosed {
|
|
||||||
return nil, errors.New("gopenpgp: can't access key packet until the message writer has been closed")
|
|
||||||
}
|
|
||||||
return res.keyPacket, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSplitStream is used to encrypt data as a stream.
|
|
||||||
// It takes a writer for the Symmetrically Encrypted Data Packet
|
|
||||||
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
|
||||||
// and returns a writer for the plaintext data and the key packet.
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptSplitStream(
|
|
||||||
dataPacketWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
) (*EncryptSplitResult, error) {
|
|
||||||
return encryptSplitStream(
|
|
||||||
keyRing,
|
|
||||||
dataPacketWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
false,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSplitStreamWithContext is used to encrypt data as a stream.
|
|
||||||
// It takes a writer for the Symmetrically Encrypted Data Packet
|
|
||||||
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
|
||||||
// and returns a writer for the plaintext data and the key packet.
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
// * signingContext : (optional) a context for the embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptSplitStreamWithContext(
|
|
||||||
dataPacketWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (*EncryptSplitResult, error) {
|
|
||||||
return encryptSplitStream(
|
|
||||||
keyRing,
|
|
||||||
dataPacketWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
false,
|
|
||||||
signingContext,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSplitStreamWithCompression is used to encrypt data as a stream.
|
|
||||||
// It takes a writer for the Symmetrically Encrypted Data Packet
|
|
||||||
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
|
||||||
// and returns a writer for the plaintext data and the key packet.
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptSplitStreamWithCompression(
|
|
||||||
dataPacketWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
) (*EncryptSplitResult, error) {
|
|
||||||
return encryptSplitStream(
|
|
||||||
keyRing,
|
|
||||||
dataPacketWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
true,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSplitStreamWithContextAndCompression is used to encrypt data as a stream.
|
|
||||||
// It takes a writer for the Symmetrically Encrypted Data Packet
|
|
||||||
// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
|
||||||
// and returns a writer for the plaintext data and the key packet.
|
|
||||||
// If signKeyRing is not nil, it is used to do an embedded signature.
|
|
||||||
// * signingContext : (optional) a context for the embedded signature.
|
|
||||||
func (keyRing *KeyRing) EncryptSplitStreamWithContextAndCompression(
|
|
||||||
dataPacketWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (*EncryptSplitResult, error) {
|
|
||||||
return encryptSplitStream(
|
|
||||||
keyRing,
|
|
||||||
dataPacketWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
true,
|
|
||||||
signingContext,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encryptSplitStream(
|
|
||||||
encryptionKeyRing *KeyRing,
|
|
||||||
dataPacketWriter Writer,
|
|
||||||
plainMessageMetadata *PlainMessageMetadata,
|
|
||||||
signKeyRing *KeyRing,
|
|
||||||
compress bool,
|
|
||||||
signingContext *SigningContext,
|
|
||||||
) (*EncryptSplitResult, error) {
|
|
||||||
var keyPacketBuf bytes.Buffer
|
|
||||||
plainMessageWriter, err := encryptStream(
|
|
||||||
encryptionKeyRing,
|
|
||||||
&keyPacketBuf,
|
|
||||||
dataPacketWriter,
|
|
||||||
plainMessageMetadata,
|
|
||||||
signKeyRing,
|
|
||||||
compress,
|
|
||||||
signingContext,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &EncryptSplitResult{
|
|
||||||
keyPacketBuf: &keyPacketBuf,
|
|
||||||
plainMessageWriter: plainMessageWriter,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlainMessageReader is used to wrap the data of the decrypted plain message.
|
|
||||||
// It can be used to read the decrypted data and verify the embedded signature.
|
|
||||||
type PlainMessageReader struct {
|
|
||||||
details *openpgp.MessageDetails
|
|
||||||
verifyKeyRing *KeyRing
|
|
||||||
verifyTime int64
|
|
||||||
readAll bool
|
|
||||||
verificationContext *VerificationContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetadata returns the metadata of the decrypted message.
|
|
||||||
func (msg *PlainMessageReader) GetMetadata() *PlainMessageMetadata {
|
|
||||||
return &PlainMessageMetadata{
|
|
||||||
Filename: msg.details.LiteralData.FileName,
|
|
||||||
IsBinary: msg.details.LiteralData.IsBinary,
|
|
||||||
ModTime: int64(msg.details.LiteralData.Time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read is used to access the message decrypted data.
|
|
||||||
// Makes PlainMessageReader implement the Reader interface.
|
|
||||||
func (msg *PlainMessageReader) Read(b []byte) (n int, err error) {
|
|
||||||
n, err = msg.details.UnverifiedBody.Read(b)
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
msg.readAll = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySignature is used to verify that the signature is valid.
|
|
||||||
// This method needs to be called once all the data has been read.
|
|
||||||
// It will return an error if the signature is invalid
|
|
||||||
// or if the message hasn't been read entirely.
|
|
||||||
func (msg *PlainMessageReader) VerifySignature() (err error) {
|
|
||||||
if !msg.readAll {
|
|
||||||
return errors.New("gopenpgp: can't verify the signature until the message reader has been read entirely")
|
|
||||||
}
|
|
||||||
if msg.verifyKeyRing != nil {
|
|
||||||
processSignatureExpiration(msg.details, msg.verifyTime)
|
|
||||||
err = verifyDetailsSignature(msg.details, msg.verifyKeyRing, msg.verificationContext)
|
|
||||||
} else {
|
|
||||||
err = errors.New("gopenpgp: no verify keyring was provided before decryption")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptStream is used to decrypt a pgp message as a Reader.
|
|
||||||
// It takes a reader for the message data
|
|
||||||
// and returns a PlainMessageReader for the plaintext data.
|
|
||||||
// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will
|
|
||||||
// verify the embedded signature with the given key ring and verification time.
|
|
||||||
func (keyRing *KeyRing) DecryptStream(
|
|
||||||
message Reader,
|
|
||||||
verifyKeyRing *KeyRing,
|
|
||||||
verifyTime int64,
|
|
||||||
) (plainMessage *PlainMessageReader, err error) {
|
|
||||||
return decryptStream(
|
|
||||||
keyRing,
|
|
||||||
message,
|
|
||||||
verifyKeyRing,
|
|
||||||
verifyTime,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptStreamWithContext is used to decrypt a pgp message as a Reader.
|
|
||||||
// It takes a reader for the message data
|
|
||||||
// and returns a PlainMessageReader for the plaintext data.
|
|
||||||
// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will
|
|
||||||
// verify the embedded signature with the given key ring and verification time.
|
|
||||||
// * verificationContext (optional): context for the signature verification.
|
|
||||||
func (keyRing *KeyRing) DecryptStreamWithContext(
|
|
||||||
message Reader,
|
|
||||||
verifyKeyRing *KeyRing,
|
|
||||||
verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) (plainMessage *PlainMessageReader, err error) {
|
|
||||||
return decryptStream(
|
|
||||||
keyRing,
|
|
||||||
message,
|
|
||||||
verifyKeyRing,
|
|
||||||
verifyTime,
|
|
||||||
verificationContext,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decryptStream(
|
|
||||||
decryptionKeyRing *KeyRing,
|
|
||||||
message Reader,
|
|
||||||
verifyKeyRing *KeyRing,
|
|
||||||
verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) (plainMessage *PlainMessageReader, err error) {
|
|
||||||
messageDetails, err := asymmetricDecryptStream(
|
|
||||||
message,
|
|
||||||
decryptionKeyRing,
|
|
||||||
verifyKeyRing,
|
|
||||||
verifyTime,
|
|
||||||
verificationContext,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PlainMessageReader{
|
|
||||||
messageDetails,
|
|
||||||
verifyKeyRing,
|
|
||||||
verifyTime,
|
|
||||||
false,
|
|
||||||
verificationContext,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptSplitStream is used to decrypt a split pgp message as a Reader.
|
|
||||||
// It takes a key packet and a reader for the data packet
|
|
||||||
// and returns a PlainMessageReader for the plaintext data.
|
|
||||||
// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will
|
|
||||||
// verify the embedded signature with the given key ring and verification time.
|
|
||||||
func (keyRing *KeyRing) DecryptSplitStream(
|
|
||||||
keypacket []byte,
|
|
||||||
dataPacketReader Reader,
|
|
||||||
verifyKeyRing *KeyRing, verifyTime int64,
|
|
||||||
) (plainMessage *PlainMessageReader, err error) {
|
|
||||||
messageReader := io.MultiReader(
|
|
||||||
bytes.NewReader(keypacket),
|
|
||||||
dataPacketReader,
|
|
||||||
)
|
|
||||||
return keyRing.DecryptStream(
|
|
||||||
messageReader,
|
|
||||||
verifyKeyRing,
|
|
||||||
verifyTime,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptSplitStreamWithContext is used to decrypt a split pgp message as a Reader.
|
|
||||||
// It takes a key packet and a reader for the data packet
|
|
||||||
// and returns a PlainMessageReader for the plaintext data.
|
|
||||||
// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will
|
|
||||||
// verify the embedded signature with the given key ring and verification time.
|
|
||||||
// * verificationContext (optional): context for the signature verification.
|
|
||||||
func (keyRing *KeyRing) DecryptSplitStreamWithContext(
|
|
||||||
keypacket []byte,
|
|
||||||
dataPacketReader Reader,
|
|
||||||
verifyKeyRing *KeyRing, verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) (plainMessage *PlainMessageReader, err error) {
|
|
||||||
messageReader := io.MultiReader(
|
|
||||||
bytes.NewReader(keypacket),
|
|
||||||
dataPacketReader,
|
|
||||||
)
|
|
||||||
return keyRing.DecryptStreamWithContext(
|
|
||||||
messageReader,
|
|
||||||
verifyKeyRing,
|
|
||||||
verifyTime,
|
|
||||||
verificationContext,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignDetachedStream generates and returns a PGPSignature for a given message Reader.
|
|
||||||
func (keyRing *KeyRing) SignDetachedStream(message Reader) (*PGPSignature, error) {
|
|
||||||
return keyRing.SignDetachedStreamWithContext(message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignDetachedStreamWithContext generates and returns a PGPSignature for a given message Reader.
|
|
||||||
// If a context is provided, it is added to the signature as notation data
|
|
||||||
// with the name set in `constants.SignatureContextName`.
|
|
||||||
func (keyRing *KeyRing) SignDetachedStreamWithContext(message Reader, context *SigningContext) (*PGPSignature, error) {
|
|
||||||
return signMessageDetached(
|
|
||||||
keyRing,
|
|
||||||
message,
|
|
||||||
true,
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetachedStream verifies a message reader with a detached PGPSignature
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
func (keyRing *KeyRing) VerifyDetachedStream(
|
|
||||||
message Reader,
|
|
||||||
signature *PGPSignature,
|
|
||||||
verifyTime int64,
|
|
||||||
) error {
|
|
||||||
_, err := verifySignature(
|
|
||||||
keyRing.entities,
|
|
||||||
message,
|
|
||||||
signature.GetBinary(),
|
|
||||||
verifyTime,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetachedStreamWithContext verifies a message reader with a detached PGPSignature
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
// If a context is provided, it verifies that the signature is valid in the given context, using
|
|
||||||
// the signature notations.
|
|
||||||
func (keyRing *KeyRing) VerifyDetachedStreamWithContext(
|
|
||||||
message Reader,
|
|
||||||
signature *PGPSignature,
|
|
||||||
verifyTime int64,
|
|
||||||
verificationContext *VerificationContext,
|
|
||||||
) error {
|
|
||||||
_, err := verifySignature(
|
|
||||||
keyRing.entities,
|
|
||||||
message,
|
|
||||||
signature.GetBinary(),
|
|
||||||
verifyTime,
|
|
||||||
verificationContext,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignDetachedEncryptedStream generates and returns a PGPMessage
|
|
||||||
// containing an encrypted detached signature for a given message Reader.
|
|
||||||
func (keyRing *KeyRing) SignDetachedEncryptedStream(
|
|
||||||
message Reader,
|
|
||||||
encryptionKeyRing *KeyRing,
|
|
||||||
) (encryptedSignature *PGPMessage, err error) {
|
|
||||||
if encryptionKeyRing == nil {
|
|
||||||
return nil, errors.New("gopenpgp: no encryption key ring provided")
|
|
||||||
}
|
|
||||||
signature, err := keyRing.SignDetachedStream(message)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
plainMessage := NewPlainMessage(signature.GetBinary())
|
|
||||||
encryptedSignature, err = encryptionKeyRing.Encrypt(plainMessage, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyDetachedEncryptedStream verifies a PlainMessage
|
|
||||||
// with a PGPMessage containing an encrypted detached signature
|
|
||||||
// and returns a SignatureVerificationError if fails.
|
|
||||||
func (keyRing *KeyRing) VerifyDetachedEncryptedStream(
|
|
||||||
message Reader,
|
|
||||||
encryptedSignature *PGPMessage,
|
|
||||||
decryptionKeyRing *KeyRing,
|
|
||||||
verifyTime int64,
|
|
||||||
) error {
|
|
||||||
if decryptionKeyRing == nil {
|
|
||||||
return errors.New("gopenpgp: no decryption key ring provided")
|
|
||||||
}
|
|
||||||
plainMessage, err := decryptionKeyRing.Decrypt(encryptedSignature, nil, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
signature := NewPGPSignature(plainMessage.GetBinary())
|
|
||||||
return keyRing.VerifyDetachedStream(message, signature, verifyTime)
|
|
||||||
}
|
|
@ -1,516 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
goerrors "errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/clearsign"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/armor"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/internal"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ---- MODELS -----
|
|
||||||
|
|
||||||
// PlainMessage stores a plain text / unencrypted message.
|
|
||||||
type PlainMessage struct {
|
|
||||||
// The content of the message
|
|
||||||
Data []byte
|
|
||||||
// If the content is text or binary
|
|
||||||
TextType bool
|
|
||||||
// The file's latest modification time
|
|
||||||
Time uint32
|
|
||||||
// The encrypted message's filename
|
|
||||||
Filename string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PGPMessage stores a PGP-encrypted message.
|
|
||||||
type PGPMessage struct {
|
|
||||||
// The content of the message
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// PGPSignature stores a PGP-encoded detached signature.
|
|
||||||
type PGPSignature struct {
|
|
||||||
// The content of the signature
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// PGPSplitMessage contains a separate session key packet and symmetrically
|
|
||||||
// encrypted data packet.
|
|
||||||
type PGPSplitMessage struct {
|
|
||||||
DataPacket []byte
|
|
||||||
KeyPacket []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ClearTextMessage is a signed but not encrypted PGP message,
|
|
||||||
// i.e. the ones beginning with -----BEGIN PGP SIGNED MESSAGE-----.
|
|
||||||
type ClearTextMessage struct {
|
|
||||||
Data []byte
|
|
||||||
Signature []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- GENERATORS -----
|
|
||||||
|
|
||||||
// NewPlainMessage generates a new binary PlainMessage ready for encryption,
|
|
||||||
// signature, or verification from the unencrypted binary data.
|
|
||||||
// This will encrypt the message with the binary flag and preserve the file as is.
|
|
||||||
func NewPlainMessage(data []byte) *PlainMessage {
|
|
||||||
return &PlainMessage{
|
|
||||||
Data: clone(data),
|
|
||||||
TextType: false,
|
|
||||||
Filename: "",
|
|
||||||
Time: uint32(GetUnixTime()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPlainMessageFromFile generates a new binary PlainMessage ready for encryption,
|
|
||||||
// signature, or verification from the unencrypted binary data.
|
|
||||||
// This will encrypt the message with the binary flag and preserve the file as is.
|
|
||||||
// It assigns a filename and a modification time.
|
|
||||||
func NewPlainMessageFromFile(data []byte, filename string, time uint32) *PlainMessage {
|
|
||||||
return &PlainMessage{
|
|
||||||
Data: clone(data),
|
|
||||||
TextType: false,
|
|
||||||
Filename: filename,
|
|
||||||
Time: time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPlainMessageFromString generates a new text PlainMessage,
|
|
||||||
// ready for encryption, signature, or verification from an unencrypted string.
|
|
||||||
// This will encrypt the message with the text flag, canonicalize the line endings
|
|
||||||
// (i.e. set all of them to \r\n) and strip the trailing spaces for each line.
|
|
||||||
// This allows seamless conversion to clear text signed messages (see RFC 4880 5.2.1 and 7.1).
|
|
||||||
func NewPlainMessageFromString(text string) *PlainMessage {
|
|
||||||
return &PlainMessage{
|
|
||||||
Data: []byte(internal.Canonicalize(text)),
|
|
||||||
TextType: true,
|
|
||||||
Filename: "",
|
|
||||||
Time: uint32(GetUnixTime()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPGPMessage generates a new PGPMessage from the unarmored binary data.
|
|
||||||
func NewPGPMessage(data []byte) *PGPMessage {
|
|
||||||
return &PGPMessage{
|
|
||||||
Data: clone(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPGPMessageFromArmored generates a new PGPMessage from an armored string ready for decryption.
|
|
||||||
func NewPGPMessageFromArmored(armored string) (*PGPMessage, error) {
|
|
||||||
encryptedIO, err := internal.Unarmor(armored)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in unarmoring message")
|
|
||||||
}
|
|
||||||
|
|
||||||
message, err := ioutil.ReadAll(encryptedIO.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in reading armored message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PGPMessage{
|
|
||||||
Data: message,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPGPSplitMessage generates a new PGPSplitMessage from the binary unarmored keypacket,
|
|
||||||
// datapacket, and encryption algorithm.
|
|
||||||
func NewPGPSplitMessage(keyPacket []byte, dataPacket []byte) *PGPSplitMessage {
|
|
||||||
return &PGPSplitMessage{
|
|
||||||
KeyPacket: clone(keyPacket),
|
|
||||||
DataPacket: clone(dataPacket),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPGPSplitMessageFromArmored generates a new PGPSplitMessage by splitting an armored message into its
|
|
||||||
// session key packet and symmetrically encrypted data packet.
|
|
||||||
func NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error) {
|
|
||||||
message, err := NewPGPMessageFromArmored(encrypted)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return message.SplitMessage()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPGPSignature generates a new PGPSignature from the unarmored binary data.
|
|
||||||
func NewPGPSignature(data []byte) *PGPSignature {
|
|
||||||
return &PGPSignature{
|
|
||||||
Data: clone(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPGPSignatureFromArmored generates a new PGPSignature from the armored
|
|
||||||
// string ready for verification.
|
|
||||||
func NewPGPSignatureFromArmored(armored string) (*PGPSignature, error) {
|
|
||||||
encryptedIO, err := internal.Unarmor(armored)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in unarmoring signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
signature, err := ioutil.ReadAll(encryptedIO.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in reading armored signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PGPSignature{
|
|
||||||
Data: signature,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClearTextMessage generates a new ClearTextMessage from data and
|
|
||||||
// signature.
|
|
||||||
func NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage {
|
|
||||||
return &ClearTextMessage{
|
|
||||||
Data: clone(data),
|
|
||||||
Signature: clone(signature),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClearTextMessageFromArmored returns the message body and unarmored
|
|
||||||
// signature from a clearsigned message.
|
|
||||||
func NewClearTextMessageFromArmored(signedMessage string) (*ClearTextMessage, error) {
|
|
||||||
modulusBlock, rest := clearsign.Decode([]byte(signedMessage))
|
|
||||||
if len(rest) != 0 {
|
|
||||||
return nil, errors.New("gopenpgp: extra data after modulus")
|
|
||||||
}
|
|
||||||
|
|
||||||
signature, err := ioutil.ReadAll(modulusBlock.ArmoredSignature.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in reading cleartext message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewClearTextMessage(modulusBlock.Bytes, signature), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- MODEL METHODS -----
|
|
||||||
|
|
||||||
// GetBinary returns the binary content of the message as a []byte.
|
|
||||||
func (msg *PlainMessage) GetBinary() []byte {
|
|
||||||
return msg.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString returns the content of the message as a string.
|
|
||||||
func (msg *PlainMessage) GetString() string {
|
|
||||||
return sanitizeString(strings.ReplaceAll(string(msg.Data), "\r\n", "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBase64 returns the base-64 encoded binary content of the message as a
|
|
||||||
// string.
|
|
||||||
func (msg *PlainMessage) GetBase64() string {
|
|
||||||
return base64.StdEncoding.EncodeToString(msg.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader returns a New io.Reader for the binary data of the message.
|
|
||||||
func (msg *PlainMessage) NewReader() io.Reader {
|
|
||||||
return bytes.NewReader(msg.GetBinary())
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsText returns whether the message is a text message.
|
|
||||||
func (msg *PlainMessage) IsText() bool {
|
|
||||||
return msg.TextType
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBinary returns whether the message is a binary message.
|
|
||||||
func (msg *PlainMessage) IsBinary() bool {
|
|
||||||
return !msg.TextType
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFormattedTime returns the message (latest modification) Time as time.Time.
|
|
||||||
func (msg *PlainMessage) getFormattedTime() time.Time {
|
|
||||||
return time.Unix(int64(msg.Time), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinary returns the unarmored binary content of the message as a []byte.
|
|
||||||
func (msg *PGPMessage) GetBinary() []byte {
|
|
||||||
return msg.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader returns a New io.Reader for the unarmored binary data of the
|
|
||||||
// message.
|
|
||||||
func (msg *PGPMessage) NewReader() io.Reader {
|
|
||||||
return bytes.NewReader(msg.GetBinary())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArmored returns the armored message as a string.
|
|
||||||
func (msg *PGPMessage) GetArmored() (string, error) {
|
|
||||||
return armor.ArmorWithType(msg.Data, constants.PGPMessageHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArmoredWithCustomHeaders returns the armored message as a string, with
|
|
||||||
// the given headers. Empty parameters are omitted from the headers.
|
|
||||||
func (msg *PGPMessage) GetArmoredWithCustomHeaders(comment, version string) (string, error) {
|
|
||||||
return armor.ArmorWithTypeAndCustomHeaders(msg.Data, constants.PGPMessageHeader, version, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEncryptionKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
|
|
||||||
func (msg *PGPMessage) GetEncryptionKeyIDs() ([]uint64, bool) {
|
|
||||||
packets := packet.NewReader(bytes.NewReader(msg.Data))
|
|
||||||
var err error
|
|
||||||
var ids []uint64
|
|
||||||
var encryptedKey *packet.EncryptedKey
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
var p packet.Packet
|
|
||||||
if p, err = packets.Next(); goerrors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch p := p.(type) {
|
|
||||||
case *packet.EncryptedKey:
|
|
||||||
encryptedKey = p
|
|
||||||
ids = append(ids, encryptedKey.KeyId)
|
|
||||||
case *packet.SymmetricallyEncrypted,
|
|
||||||
*packet.AEADEncrypted,
|
|
||||||
*packet.Compressed,
|
|
||||||
*packet.LiteralData:
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(ids) > 0 {
|
|
||||||
return ids, true
|
|
||||||
}
|
|
||||||
return ids, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHexEncryptionKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
|
|
||||||
func (msg *PGPMessage) GetHexEncryptionKeyIDs() ([]string, bool) {
|
|
||||||
return getHexKeyIDs(msg.GetEncryptionKeyIDs())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHexEncryptionKeyIDsJson returns the key IDs of the keys to which the session key is encrypted as a JSON array.
|
|
||||||
// If an error occurs it returns nil.
|
|
||||||
// Helper function for go-mobile clients.
|
|
||||||
func (msg *PGPMessage) GetHexEncryptionKeyIDsJson() []byte {
|
|
||||||
hexIds, ok := msg.GetHexEncryptionKeyIDs()
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
hexIdsJson, err := json.Marshal(hexIds)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return hexIdsJson
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignatureKeyIDs Returns the key IDs of the keys to which the (readable) signature packets are encrypted to.
|
|
||||||
func (msg *PGPMessage) GetSignatureKeyIDs() ([]uint64, bool) {
|
|
||||||
return getSignatureKeyIDs(msg.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHexSignatureKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
|
|
||||||
func (msg *PGPMessage) GetHexSignatureKeyIDs() ([]string, bool) {
|
|
||||||
return getHexKeyIDs(msg.GetSignatureKeyIDs())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHexSignatureKeyIDsJson returns the key IDs of the keys to which the (readable) signature packets
|
|
||||||
// are encrypted to as a JSON array. Helper function for go-mobile clients.
|
|
||||||
func (msg *PGPMessage) GetHexSignatureKeyIDsJson() []byte {
|
|
||||||
sigHexSigIds, ok := msg.GetHexSignatureKeyIDs()
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sigHexKeyIdsJSON, err := json.Marshal(sigHexSigIds)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return sigHexKeyIdsJSON
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinaryDataPacket returns the unarmored binary datapacket as a []byte.
|
|
||||||
func (msg *PGPSplitMessage) GetBinaryDataPacket() []byte {
|
|
||||||
return msg.DataPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinaryKeyPacket returns the unarmored binary keypacket as a []byte.
|
|
||||||
func (msg *PGPSplitMessage) GetBinaryKeyPacket() []byte {
|
|
||||||
return msg.KeyPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinary returns the unarmored binary joined packets as a []byte.
|
|
||||||
func (msg *PGPSplitMessage) GetBinary() []byte {
|
|
||||||
return append(msg.KeyPacket, msg.DataPacket...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArmored returns the armored message as a string, with joined data and key
|
|
||||||
// packets.
|
|
||||||
func (msg *PGPSplitMessage) GetArmored() (string, error) {
|
|
||||||
return armor.ArmorWithType(msg.GetBinary(), constants.PGPMessageHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPGPMessage joins asymmetric session key packet with the symmetric data
|
|
||||||
// packet to obtain a PGP message.
|
|
||||||
func (msg *PGPSplitMessage) GetPGPMessage() *PGPMessage {
|
|
||||||
return NewPGPMessage(append(msg.KeyPacket, msg.DataPacket...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNumberOfKeyPackets returns the number of keys packets in this message.
|
|
||||||
func (msg *PGPSplitMessage) GetNumberOfKeyPackets() (int, error) {
|
|
||||||
bytesReader := bytes.NewReader(msg.KeyPacket)
|
|
||||||
packets := packet.NewReader(bytesReader)
|
|
||||||
var keyPacketCount int
|
|
||||||
for {
|
|
||||||
p, err := packets.Next()
|
|
||||||
if goerrors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch p.(type) {
|
|
||||||
case *packet.SymmetricKeyEncrypted, *packet.EncryptedKey:
|
|
||||||
keyPacketCount += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keyPacketCount, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitMessage splits the message into key and data packet(s).
|
|
||||||
// Parameters are for backwards compatibility and are unused.
|
|
||||||
func (msg *PGPMessage) SplitMessage() (*PGPSplitMessage, error) {
|
|
||||||
bytesReader := bytes.NewReader(msg.Data)
|
|
||||||
packets := packet.NewReader(bytesReader)
|
|
||||||
splitPoint := int64(0)
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
p, err := packets.Next()
|
|
||||||
if goerrors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch p.(type) {
|
|
||||||
case *packet.SymmetricKeyEncrypted, *packet.EncryptedKey:
|
|
||||||
splitPoint = bytesReader.Size() - int64(bytesReader.Len())
|
|
||||||
case *packet.SymmetricallyEncrypted, *packet.AEADEncrypted:
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &PGPSplitMessage{
|
|
||||||
KeyPacket: clone(msg.Data[:splitPoint]),
|
|
||||||
DataPacket: clone(msg.Data[splitPoint:]),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SeparateKeyAndData splits the message into key and data packet(s).
|
|
||||||
// Parameters are for backwards compatibility and are unused.
|
|
||||||
// Deprecated: use SplitMessage().
|
|
||||||
func (msg *PGPMessage) SeparateKeyAndData(_ int, _ int) (*PGPSplitMessage, error) {
|
|
||||||
return msg.SplitMessage()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinary returns the unarmored binary content of the signature as a []byte.
|
|
||||||
func (sig *PGPSignature) GetBinary() []byte {
|
|
||||||
return sig.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArmored returns the armored signature as a string.
|
|
||||||
func (sig *PGPSignature) GetArmored() (string, error) {
|
|
||||||
return armor.ArmorWithType(sig.Data, constants.PGPSignatureHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignatureKeyIDs Returns the key IDs of the keys to which the (readable) signature packets are encrypted to.
|
|
||||||
func (sig *PGPSignature) GetSignatureKeyIDs() ([]uint64, bool) {
|
|
||||||
return getSignatureKeyIDs(sig.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHexSignatureKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
|
|
||||||
func (sig *PGPSignature) GetHexSignatureKeyIDs() ([]string, bool) {
|
|
||||||
return getHexKeyIDs(sig.GetSignatureKeyIDs())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinary returns the unarmored signed data as a []byte.
|
|
||||||
func (msg *ClearTextMessage) GetBinary() []byte {
|
|
||||||
return msg.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString returns the unarmored signed data as a string.
|
|
||||||
func (msg *ClearTextMessage) GetString() string {
|
|
||||||
return string(msg.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinarySignature returns the unarmored binary signature as a []byte.
|
|
||||||
func (msg *ClearTextMessage) GetBinarySignature() []byte {
|
|
||||||
return msg.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArmored armors plaintext and signature with the PGP SIGNED MESSAGE
|
|
||||||
// armoring.
|
|
||||||
func (msg *ClearTextMessage) GetArmored() (string, error) {
|
|
||||||
armSignature, err := armor.ArmorWithType(msg.GetBinarySignature(), constants.PGPSignatureHeader)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "gopenpgp: error in armoring cleartext message")
|
|
||||||
}
|
|
||||||
|
|
||||||
str := "-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: SHA512\r\n\r\n"
|
|
||||||
str += msg.GetString()
|
|
||||||
str += "\r\n"
|
|
||||||
str += armSignature
|
|
||||||
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- UTILS -----
|
|
||||||
|
|
||||||
// IsPGPMessage checks if data if has armored PGP message format.
|
|
||||||
func IsPGPMessage(data string) bool {
|
|
||||||
re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " +
|
|
||||||
constants.PGPMessageHeader + "-----")
|
|
||||||
return re.MatchString(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSignatureKeyIDs(data []byte) ([]uint64, bool) {
|
|
||||||
packets := packet.NewReader(bytes.NewReader(data))
|
|
||||||
var err error
|
|
||||||
var ids []uint64
|
|
||||||
var onePassSignaturePacket *packet.OnePassSignature
|
|
||||||
var signaturePacket *packet.Signature
|
|
||||||
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
var p packet.Packet
|
|
||||||
if p, err = packets.Next(); goerrors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch p := p.(type) {
|
|
||||||
case *packet.OnePassSignature:
|
|
||||||
onePassSignaturePacket = p
|
|
||||||
ids = append(ids, onePassSignaturePacket.KeyId)
|
|
||||||
case *packet.Signature:
|
|
||||||
signaturePacket = p
|
|
||||||
if signaturePacket.IssuerKeyId != nil {
|
|
||||||
ids = append(ids, *signaturePacket.IssuerKeyId)
|
|
||||||
}
|
|
||||||
case *packet.SymmetricallyEncrypted,
|
|
||||||
*packet.AEADEncrypted,
|
|
||||||
*packet.Compressed,
|
|
||||||
*packet.LiteralData:
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(ids) > 0 {
|
|
||||||
return ids, true
|
|
||||||
}
|
|
||||||
return ids, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHexKeyIDs(keyIDs []uint64, ok bool) ([]string, bool) {
|
|
||||||
hexIDs := make([]string, len(keyIDs))
|
|
||||||
|
|
||||||
for i, id := range keyIDs {
|
|
||||||
hexIDs[i] = keyIDToHex(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hexIDs, ok
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
//go:build !android
|
|
||||||
// +build !android
|
|
||||||
|
|
||||||
package crypto
|
|
||||||
|
|
||||||
// GetFilename returns the file name of the message as a string.
|
|
||||||
func (msg *PlainMessage) GetFilename() string {
|
|
||||||
return msg.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTime returns the modification time of a file (if provided in the ciphertext).
|
|
||||||
func (msg *PlainMessage) GetTime() uint32 {
|
|
||||||
return msg.Time
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/mail"
|
|
||||||
"net/textproto"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
gomime "github.com/ProtonMail/go-mime"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MIMECallbacks defines callback methods to process a MIME message.
|
|
||||||
type MIMECallbacks interface {
|
|
||||||
OnBody(body string, mimetype string)
|
|
||||||
OnAttachment(headers string, data []byte)
|
|
||||||
// Encrypted headers can be in an attachment and thus be placed at the end of the mime structure.
|
|
||||||
OnEncryptedHeaders(headers string)
|
|
||||||
OnVerified(verified int)
|
|
||||||
OnError(err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptMIMEMessage decrypts a MIME message.
|
|
||||||
func (keyRing *KeyRing) DecryptMIMEMessage(
|
|
||||||
message *PGPMessage, verifyKey *KeyRing, callbacks MIMECallbacks, verifyTime int64,
|
|
||||||
) {
|
|
||||||
decryptedMessage, err := keyRing.Decrypt(message, verifyKey, verifyTime)
|
|
||||||
embeddedSigError, err := separateSigError(err)
|
|
||||||
if err != nil {
|
|
||||||
callbacks.OnError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
body, attachments, attachmentHeaders, err := parseMIME(string(decryptedMessage.GetBinary()), verifyKey)
|
|
||||||
mimeSigError, err := separateSigError(err)
|
|
||||||
if err != nil {
|
|
||||||
callbacks.OnError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// We only consider the signature to be failed if both embedded and mime verification failed
|
|
||||||
if embeddedSigError != nil && mimeSigError != nil {
|
|
||||||
callbacks.OnError(embeddedSigError)
|
|
||||||
callbacks.OnError(mimeSigError)
|
|
||||||
callbacks.OnVerified(prioritizeSignatureErrors(embeddedSigError, mimeSigError))
|
|
||||||
} else if verifyKey != nil {
|
|
||||||
callbacks.OnVerified(constants.SIGNATURE_OK)
|
|
||||||
}
|
|
||||||
bodyContent, bodyMimeType := body.GetBody()
|
|
||||||
bodyContentSanitized := sanitizeString(bodyContent)
|
|
||||||
callbacks.OnBody(bodyContentSanitized, bodyMimeType)
|
|
||||||
for i := 0; i < len(attachments); i++ {
|
|
||||||
callbacks.OnAttachment(attachmentHeaders[i], []byte(attachments[i]))
|
|
||||||
}
|
|
||||||
callbacks.OnEncryptedHeaders("")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- INTERNAL FUNCTIONS -----
|
|
||||||
|
|
||||||
func prioritizeSignatureErrors(signatureErrs ...*SignatureVerificationError) (maxError int) {
|
|
||||||
// select error with the highest value, if any
|
|
||||||
// FAILED > NO VERIFIER > NOT SIGNED > SIGNATURE OK
|
|
||||||
maxError = constants.SIGNATURE_OK
|
|
||||||
for _, err := range signatureErrs {
|
|
||||||
if err.Status > maxError {
|
|
||||||
maxError = err.Status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func separateSigError(err error) (*SignatureVerificationError, error) {
|
|
||||||
sigErr := &SignatureVerificationError{}
|
|
||||||
if errors.As(err, sigErr) {
|
|
||||||
return sigErr, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMIME(
|
|
||||||
mimeBody string, verifierKey *KeyRing,
|
|
||||||
) (*gomime.BodyCollector, []string, []string, error) {
|
|
||||||
mm, err := mail.ReadMessage(strings.NewReader(mimeBody))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, errors.Wrap(err, "gopenpgp: error in reading message")
|
|
||||||
}
|
|
||||||
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
|
|
||||||
|
|
||||||
h := textproto.MIMEHeader(mm.Header)
|
|
||||||
mmBodyData, err := ioutil.ReadAll(mm.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, errors.Wrap(err, "gopenpgp: error in reading message body data")
|
|
||||||
}
|
|
||||||
|
|
||||||
printAccepter := gomime.NewMIMEPrinter()
|
|
||||||
bodyCollector := gomime.NewBodyCollector(printAccepter)
|
|
||||||
attachmentsCollector := gomime.NewAttachmentsCollector(bodyCollector)
|
|
||||||
mimeVisitor := gomime.NewMimeVisitor(attachmentsCollector)
|
|
||||||
|
|
||||||
var verifierEntities openpgp.KeyRing
|
|
||||||
if verifierKey != nil {
|
|
||||||
verifierEntities = verifierKey.entities
|
|
||||||
}
|
|
||||||
|
|
||||||
signatureCollector := newSignatureCollector(mimeVisitor, verifierEntities, config)
|
|
||||||
|
|
||||||
err = gomime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector)
|
|
||||||
if err == nil && verifierKey != nil {
|
|
||||||
err = signatureCollector.verified
|
|
||||||
}
|
|
||||||
|
|
||||||
return bodyCollector,
|
|
||||||
attachmentsCollector.GetAttachments(),
|
|
||||||
attachmentsCollector.GetAttHeaders(),
|
|
||||||
err
|
|
||||||
}
|
|
@ -1,179 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
pgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EncryptMessageWithPassword encrypts a PlainMessage to PGPMessage with a
|
|
||||||
// SymmetricKey.
|
|
||||||
// * message : The plain data as a PlainMessage.
|
|
||||||
// * password: A password that will be derived into an encryption key.
|
|
||||||
// * output : The encrypted data as PGPMessage.
|
|
||||||
func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error) {
|
|
||||||
encrypted, err := passwordEncrypt(message, password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewPGPMessage(encrypted), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptMessageWithPassword decrypts password protected pgp binary messages.
|
|
||||||
// * encrypted: The encrypted data as PGPMessage.
|
|
||||||
// * password: A password that will be derived into an encryption key.
|
|
||||||
// * output: The decrypted data as PlainMessage.
|
|
||||||
func DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error) {
|
|
||||||
return passwordDecrypt(message.NewReader(), password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptSessionKeyWithPassword decrypts the binary symmetrically encrypted
|
|
||||||
// session key packet and returns the session key.
|
|
||||||
func DecryptSessionKeyWithPassword(keyPacket, password []byte) (*SessionKey, error) {
|
|
||||||
keyReader := bytes.NewReader(keyPacket)
|
|
||||||
packets := packet.NewReader(keyReader)
|
|
||||||
|
|
||||||
var symKeys []*packet.SymmetricKeyEncrypted
|
|
||||||
for {
|
|
||||||
var p packet.Packet
|
|
||||||
var err error
|
|
||||||
if p, err = packets.Next(); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if p, ok := p.(*packet.SymmetricKeyEncrypted); ok {
|
|
||||||
symKeys = append(symKeys, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try the symmetric passphrase first
|
|
||||||
if len(symKeys) != 0 && password != nil {
|
|
||||||
for _, s := range symKeys {
|
|
||||||
key, cipherFunc, err := s.Decrypt(password)
|
|
||||||
if err == nil {
|
|
||||||
sk := &SessionKey{
|
|
||||||
Key: key,
|
|
||||||
Algo: getAlgo(cipherFunc),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sk.checkSize(); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt session key with password")
|
|
||||||
}
|
|
||||||
|
|
||||||
return sk, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("gopenpgp: unable to decrypt any packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptSessionKeyWithPassword encrypts the session key with the password and
|
|
||||||
// returns a binary symmetrically encrypted session key packet.
|
|
||||||
func EncryptSessionKeyWithPassword(sk *SessionKey, password []byte) ([]byte, error) {
|
|
||||||
outbuf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
cf, err := sk.GetCipherFunc()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(password) == 0 {
|
|
||||||
return nil, errors.New("gopenpgp: password can't be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sk.checkSize(); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &packet.Config{
|
|
||||||
DefaultCipher: cf,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, sk.Key, password, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
|
|
||||||
}
|
|
||||||
return outbuf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- INTERNAL FUNCTIONS ------
|
|
||||||
|
|
||||||
func passwordEncrypt(message *PlainMessage, password []byte) ([]byte, error) {
|
|
||||||
var outBuf bytes.Buffer
|
|
||||||
|
|
||||||
config := &packet.Config{
|
|
||||||
DefaultCipher: packet.CipherAES256,
|
|
||||||
Time: getTimeGenerator(),
|
|
||||||
}
|
|
||||||
|
|
||||||
hints := &openpgp.FileHints{
|
|
||||||
IsBinary: message.IsBinary(),
|
|
||||||
FileName: message.Filename,
|
|
||||||
ModTime: message.getFormattedTime(),
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, password, hints, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in encrypting message symmetrically")
|
|
||||||
}
|
|
||||||
_, err = encryptWriter.Write(message.GetBinary())
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in writing data to message")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = encryptWriter.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "gopenpgp: error in closing writer")
|
|
||||||
}
|
|
||||||
|
|
||||||
return outBuf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func passwordDecrypt(encryptedIO io.Reader, password []byte) (*PlainMessage, error) {
|
|
||||||
firstTimeCalled := true
|
|
||||||
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
|
||||||
if firstTimeCalled {
|
|
||||||
firstTimeCalled = false
|
|
||||||
return password, nil
|
|
||||||
}
|
|
||||||
// Re-prompt still occurs if SKESK pasrsing fails (i.e. when decrypted cipher algo is invalid).
|
|
||||||
// For most (but not all) cases, inputting a wrong passwords is expected to trigger this error.
|
|
||||||
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &packet.Config{
|
|
||||||
Time: getTimeGenerator(),
|
|
||||||
}
|
|
||||||
|
|
||||||
var emptyKeyRing openpgp.EntityList
|
|
||||||
md, err := openpgp.ReadMessage(encryptedIO, emptyKeyRing, prompt, config)
|
|
||||||
if err != nil {
|
|
||||||
// Parsing errors when reading the message are most likely caused by incorrect password, but we cannot know for sure
|
|
||||||
return nil, errors.New("gopenpgp: error in reading password protected message: wrong password or malformed message")
|
|
||||||
}
|
|
||||||
|
|
||||||
messageBuf := bytes.NewBuffer(nil)
|
|
||||||
_, err = io.Copy(messageBuf, md.UnverifiedBody)
|
|
||||||
if errors.Is(err, pgpErrors.ErrMDCHashMismatch) {
|
|
||||||
// This MDC error may also be triggered if the password is correct, but the encrypted data was corrupted.
|
|
||||||
// To avoid confusion, we do not inform the user about the second possibility.
|
|
||||||
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// Parsing errors after decryption, triggered before parsing the MDC packet, are also usually the result of wrong password
|
|
||||||
return nil, errors.New("gopenpgp: error in reading password protected message: wrong password or malformed message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PlainMessage{
|
|
||||||
Data: messageBuf.Bytes(),
|
|
||||||
TextType: !md.LiteralData.IsBinary,
|
|
||||||
Filename: md.LiteralData.FileName,
|
|
||||||
Time: md.LiteralData.Time,
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func sanitizeString(input string) string {
|
|
||||||
return strings.ToValidUTF8(input, "\ufffd")
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue