Add a new generic container registry client via a new `container` package. Use this to create a command line utility as well as a new upload target for container registries. The code uses the github.com/containers/* project and packages to interact with container registires that is also used by skopeo, podman et al. One if the dependencies is `proglottis/gpgme` that is using cgo to bind libgpgme, so we have to add the corresponding devel package to the BuildRequires as well as installing it on CI. Checks will follow later via an integration test.
422 lines
12 KiB
Go
422 lines
12 KiB
Go
package libtrust
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
)
|
|
|
|
/*
|
|
* EC DSA PUBLIC KEY
|
|
*/
|
|
|
|
// ecPublicKey implements a libtrust.PublicKey using elliptic curve digital
|
|
// signature algorithms.
|
|
type ecPublicKey struct {
|
|
*ecdsa.PublicKey
|
|
curveName string
|
|
signatureAlgorithm *signatureAlgorithm
|
|
extended map[string]interface{}
|
|
}
|
|
|
|
func fromECPublicKey(cryptoPublicKey *ecdsa.PublicKey) (*ecPublicKey, error) {
|
|
curve := cryptoPublicKey.Curve
|
|
|
|
switch {
|
|
case curve == elliptic.P256():
|
|
return &ecPublicKey{cryptoPublicKey, "P-256", es256, map[string]interface{}{}}, nil
|
|
case curve == elliptic.P384():
|
|
return &ecPublicKey{cryptoPublicKey, "P-384", es384, map[string]interface{}{}}, nil
|
|
case curve == elliptic.P521():
|
|
return &ecPublicKey{cryptoPublicKey, "P-521", es512, map[string]interface{}{}}, nil
|
|
default:
|
|
return nil, errors.New("unsupported elliptic curve")
|
|
}
|
|
}
|
|
|
|
// KeyType returns the key type for elliptic curve keys, i.e., "EC".
|
|
func (k *ecPublicKey) KeyType() string {
|
|
return "EC"
|
|
}
|
|
|
|
// CurveName returns the elliptic curve identifier.
|
|
// Possible values are "P-256", "P-384", and "P-521".
|
|
func (k *ecPublicKey) CurveName() string {
|
|
return k.curveName
|
|
}
|
|
|
|
// KeyID returns a distinct identifier which is unique to this Public Key.
|
|
func (k *ecPublicKey) KeyID() string {
|
|
return keyIDFromCryptoKey(k)
|
|
}
|
|
|
|
func (k *ecPublicKey) String() string {
|
|
return fmt.Sprintf("EC Public Key <%s>", k.KeyID())
|
|
}
|
|
|
|
// Verify verifyies the signature of the data in the io.Reader using this
|
|
// PublicKey. The alg parameter should identify the digital signature
|
|
// algorithm which was used to produce the signature and should be supported
|
|
// by this public key. Returns a nil error if the signature is valid.
|
|
func (k *ecPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
|
|
// For EC keys there is only one supported signature algorithm depending
|
|
// on the curve parameters.
|
|
if k.signatureAlgorithm.HeaderParam() != alg {
|
|
return fmt.Errorf("unable to verify signature: EC Public Key with curve %q does not support signature algorithm %q", k.curveName, alg)
|
|
}
|
|
|
|
// signature is the concatenation of (r, s), base64Url encoded.
|
|
sigLength := len(signature)
|
|
expectedOctetLength := 2 * ((k.Params().BitSize + 7) >> 3)
|
|
if sigLength != expectedOctetLength {
|
|
return fmt.Errorf("signature length is %d octets long, should be %d", sigLength, expectedOctetLength)
|
|
}
|
|
|
|
rBytes, sBytes := signature[:sigLength/2], signature[sigLength/2:]
|
|
r := new(big.Int).SetBytes(rBytes)
|
|
s := new(big.Int).SetBytes(sBytes)
|
|
|
|
hasher := k.signatureAlgorithm.HashID().New()
|
|
_, err := io.Copy(hasher, data)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading data to sign: %s", err)
|
|
}
|
|
hash := hasher.Sum(nil)
|
|
|
|
if !ecdsa.Verify(k.PublicKey, hash, r, s) {
|
|
return errors.New("invalid signature")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CryptoPublicKey returns the internal object which can be used as a
|
|
// crypto.PublicKey for use with other standard library operations. The type
|
|
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
func (k *ecPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
return k.PublicKey
|
|
}
|
|
|
|
func (k *ecPublicKey) toMap() map[string]interface{} {
|
|
jwk := make(map[string]interface{})
|
|
for k, v := range k.extended {
|
|
jwk[k] = v
|
|
}
|
|
jwk["kty"] = k.KeyType()
|
|
jwk["kid"] = k.KeyID()
|
|
jwk["crv"] = k.CurveName()
|
|
|
|
xBytes := k.X.Bytes()
|
|
yBytes := k.Y.Bytes()
|
|
octetLength := (k.Params().BitSize + 7) >> 3
|
|
// MUST include leading zeros in the output so that x, y are each
|
|
// *octetLength* bytes long.
|
|
xBuf := make([]byte, octetLength-len(xBytes), octetLength)
|
|
yBuf := make([]byte, octetLength-len(yBytes), octetLength)
|
|
xBuf = append(xBuf, xBytes...)
|
|
yBuf = append(yBuf, yBytes...)
|
|
|
|
jwk["x"] = joseBase64UrlEncode(xBuf)
|
|
jwk["y"] = joseBase64UrlEncode(yBuf)
|
|
|
|
return jwk
|
|
}
|
|
|
|
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
|
|
// elliptic curve keys.
|
|
func (k *ecPublicKey) MarshalJSON() (data []byte, err error) {
|
|
return json.Marshal(k.toMap())
|
|
}
|
|
|
|
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
|
|
func (k *ecPublicKey) PEMBlock() (*pem.Block, error) {
|
|
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err)
|
|
}
|
|
k.extended["kid"] = k.KeyID() // For display purposes.
|
|
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
|
|
}
|
|
|
|
func (k *ecPublicKey) AddExtendedField(field string, value interface{}) {
|
|
k.extended[field] = value
|
|
}
|
|
|
|
func (k *ecPublicKey) GetExtendedField(field string) interface{} {
|
|
v, ok := k.extended[field]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
|
|
func ecPublicKeyFromMap(jwk map[string]interface{}) (*ecPublicKey, error) {
|
|
// JWK key type (kty) has already been determined to be "EC".
|
|
// Need to extract 'crv', 'x', 'y', and 'kid' and check for
|
|
// consistency.
|
|
|
|
// Get the curve identifier value.
|
|
crv, err := stringFromMap(jwk, "crv")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Public Key curve identifier: %s", err)
|
|
}
|
|
|
|
var (
|
|
curve elliptic.Curve
|
|
sigAlg *signatureAlgorithm
|
|
)
|
|
|
|
switch {
|
|
case crv == "P-256":
|
|
curve = elliptic.P256()
|
|
sigAlg = es256
|
|
case crv == "P-384":
|
|
curve = elliptic.P384()
|
|
sigAlg = es384
|
|
case crv == "P-521":
|
|
curve = elliptic.P521()
|
|
sigAlg = es512
|
|
default:
|
|
return nil, fmt.Errorf("JWK EC Public Key curve identifier not supported: %q\n", crv)
|
|
}
|
|
|
|
// Get the X and Y coordinates for the public key point.
|
|
xB64Url, err := stringFromMap(jwk, "x")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
|
|
}
|
|
x, err := parseECCoordinate(xB64Url, curve)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
|
|
}
|
|
|
|
yB64Url, err := stringFromMap(jwk, "y")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
|
|
}
|
|
y, err := parseECCoordinate(yB64Url, curve)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
|
|
}
|
|
|
|
key := &ecPublicKey{
|
|
PublicKey: &ecdsa.PublicKey{Curve: curve, X: x, Y: y},
|
|
curveName: crv, signatureAlgorithm: sigAlg,
|
|
}
|
|
|
|
// Key ID is optional too, but if it exists, it should match the key.
|
|
_, ok := jwk["kid"]
|
|
if ok {
|
|
kid, err := stringFromMap(jwk, "kid")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Public Key ID: %s", err)
|
|
}
|
|
if kid != key.KeyID() {
|
|
return nil, fmt.Errorf("JWK EC Public Key ID does not match: %s", kid)
|
|
}
|
|
}
|
|
|
|
key.extended = jwk
|
|
|
|
return key, nil
|
|
}
|
|
|
|
/*
|
|
* EC DSA PRIVATE KEY
|
|
*/
|
|
|
|
// ecPrivateKey implements a JWK Private Key using elliptic curve digital signature
|
|
// algorithms.
|
|
type ecPrivateKey struct {
|
|
ecPublicKey
|
|
*ecdsa.PrivateKey
|
|
}
|
|
|
|
func fromECPrivateKey(cryptoPrivateKey *ecdsa.PrivateKey) (*ecPrivateKey, error) {
|
|
publicKey, err := fromECPublicKey(&cryptoPrivateKey.PublicKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ecPrivateKey{*publicKey, cryptoPrivateKey}, nil
|
|
}
|
|
|
|
// PublicKey returns the Public Key data associated with this Private Key.
|
|
func (k *ecPrivateKey) PublicKey() PublicKey {
|
|
return &k.ecPublicKey
|
|
}
|
|
|
|
func (k *ecPrivateKey) String() string {
|
|
return fmt.Sprintf("EC Private Key <%s>", k.KeyID())
|
|
}
|
|
|
|
// Sign signs the data read from the io.Reader using a signature algorithm supported
|
|
// by the elliptic curve private key. If the specified hashing algorithm is
|
|
// supported by this key, that hash function is used to generate the signature
|
|
// otherwise the the default hashing algorithm for this key is used. Returns
|
|
// the signature and the name of the JWK signature algorithm used, e.g.,
|
|
// "ES256", "ES384", "ES512".
|
|
func (k *ecPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
|
|
// Generate a signature of the data using the internal alg.
|
|
// The given hashId is only a suggestion, and since EC keys only support
|
|
// on signature/hash algorithm given the curve name, we disregard it for
|
|
// the elliptic curve JWK signature implementation.
|
|
r, s, err := k.sign(data, hashID)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("error producing signature: %s", err)
|
|
}
|
|
|
|
rBytes, sBytes := r.Bytes(), s.Bytes()
|
|
octetLength := (k.ecPublicKey.Params().BitSize + 7) >> 3
|
|
// MUST include leading zeros in the output
|
|
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
|
|
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
|
|
|
|
rBuf = append(rBuf, rBytes...)
|
|
sBuf = append(sBuf, sBytes...)
|
|
|
|
signature = append(rBuf, sBuf...)
|
|
alg = k.signatureAlgorithm.HeaderParam()
|
|
|
|
return
|
|
}
|
|
|
|
// CryptoPrivateKey returns the internal object which can be used as a
|
|
// crypto.PublicKey for use with other standard library operations. The type
|
|
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
func (k *ecPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
|
|
return k.PrivateKey
|
|
}
|
|
|
|
func (k *ecPrivateKey) toMap() map[string]interface{} {
|
|
jwk := k.ecPublicKey.toMap()
|
|
|
|
dBytes := k.D.Bytes()
|
|
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
|
|
// octets (where n is the order of the curve). This is because the private
|
|
// key d must be in the interval [1, n-1] so the bitlength of d should be
|
|
// no larger than the bitlength of n-1. The easiest way to find the octet
|
|
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
|
|
// bit sequence right by 3, which is essentially dividing by 8 and adding
|
|
// 1 if there is any remainder. Thus, the private key value d should be
|
|
// output to (bitlength(n-1)+7)>>3 octets.
|
|
n := k.ecPublicKey.Params().N
|
|
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
|
|
// Create a buffer with the necessary zero-padding.
|
|
dBuf := make([]byte, octetLength-len(dBytes), octetLength)
|
|
dBuf = append(dBuf, dBytes...)
|
|
|
|
jwk["d"] = joseBase64UrlEncode(dBuf)
|
|
|
|
return jwk
|
|
}
|
|
|
|
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
|
|
// elliptic curve keys.
|
|
func (k *ecPrivateKey) MarshalJSON() (data []byte, err error) {
|
|
return json.Marshal(k.toMap())
|
|
}
|
|
|
|
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
|
|
func (k *ecPrivateKey) PEMBlock() (*pem.Block, error) {
|
|
derBytes, err := x509.MarshalECPrivateKey(k.PrivateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to serialize EC PrivateKey to DER-encoded PKIX format: %s", err)
|
|
}
|
|
k.extended["keyID"] = k.KeyID() // For display purposes.
|
|
return createPemBlock("EC PRIVATE KEY", derBytes, k.extended)
|
|
}
|
|
|
|
func ecPrivateKeyFromMap(jwk map[string]interface{}) (*ecPrivateKey, error) {
|
|
dB64Url, err := stringFromMap(jwk, "d")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Private Key: %s", err)
|
|
}
|
|
|
|
// JWK key type (kty) has already been determined to be "EC".
|
|
// Need to extract the public key information, then extract the private
|
|
// key value 'd'.
|
|
publicKey, err := ecPublicKeyFromMap(jwk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d, err := parseECPrivateParam(dB64Url, publicKey.Curve)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("JWK EC Private Key d-param: %s", err)
|
|
}
|
|
|
|
key := &ecPrivateKey{
|
|
ecPublicKey: *publicKey,
|
|
PrivateKey: &ecdsa.PrivateKey{
|
|
PublicKey: *publicKey.PublicKey,
|
|
D: d,
|
|
},
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
/*
|
|
* Key Generation Functions.
|
|
*/
|
|
|
|
func generateECPrivateKey(curve elliptic.Curve) (k *ecPrivateKey, err error) {
|
|
k = new(ecPrivateKey)
|
|
k.PrivateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
k.ecPublicKey.PublicKey = &k.PrivateKey.PublicKey
|
|
k.extended = make(map[string]interface{})
|
|
|
|
return
|
|
}
|
|
|
|
// GenerateECP256PrivateKey generates a key pair using elliptic curve P-256.
|
|
func GenerateECP256PrivateKey() (PrivateKey, error) {
|
|
k, err := generateECPrivateKey(elliptic.P256())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error generating EC P-256 key: %s", err)
|
|
}
|
|
|
|
k.curveName = "P-256"
|
|
k.signatureAlgorithm = es256
|
|
|
|
return k, nil
|
|
}
|
|
|
|
// GenerateECP384PrivateKey generates a key pair using elliptic curve P-384.
|
|
func GenerateECP384PrivateKey() (PrivateKey, error) {
|
|
k, err := generateECPrivateKey(elliptic.P384())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error generating EC P-384 key: %s", err)
|
|
}
|
|
|
|
k.curveName = "P-384"
|
|
k.signatureAlgorithm = es384
|
|
|
|
return k, nil
|
|
}
|
|
|
|
// GenerateECP521PrivateKey generates aß key pair using elliptic curve P-521.
|
|
func GenerateECP521PrivateKey() (PrivateKey, error) {
|
|
k, err := generateECPrivateKey(elliptic.P521())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error generating EC P-521 key: %s", err)
|
|
}
|
|
|
|
k.curveName = "P-521"
|
|
k.signatureAlgorithm = es512
|
|
|
|
return k, nil
|
|
}
|