portainer/portainer

View on GitHub
api/crypto/ecdsa.go

Summary

Maintainability
A
0 mins
Test Coverage
package crypto

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/x509"
    "encoding/base64"
    "encoding/hex"

    "github.com/portainer/portainer/pkg/libcrypto"
)

const (
    // PrivateKeyPemHeader represents the header that is appended to the PEM file when
    // storing the private key.
    PrivateKeyPemHeader = "EC PRIVATE KEY"
    // PublicKeyPemHeader represents the header that is appended to the PEM file when
    // storing the public key.
    PublicKeyPemHeader = "ECDSA PUBLIC KEY"
)

// ECDSAService is a service used to create digital signatures when communicating with
// an agent based environment(endpoint). It will automatically generates a key pair using ECDSA or
// can also reuse an existing ECDSA key pair.
type ECDSAService struct {
    privateKey    *ecdsa.PrivateKey
    publicKey     *ecdsa.PublicKey
    encodedPubKey string
    secret        string
}

// NewECDSAService returns a pointer to a ECDSAService.
// An optional secret can be specified
func NewECDSAService(secret string) *ECDSAService {
    return &ECDSAService{
        secret: secret,
    }
}

// EncodedPublicKey returns the encoded version of the public that can be used
// to be shared with other services. It's the hexadecimal encoding of the public key
// content.
func (service *ECDSAService) EncodedPublicKey() string {
    return service.encodedPubKey
}

// PEMHeaders returns the ECDSA PEM headers.
func (service *ECDSAService) PEMHeaders() (string, string) {
    return PrivateKeyPemHeader, PublicKeyPemHeader
}

// ParseKeyPair parses existing private/public key pair content and associate
// the parsed keys to the service.
func (service *ECDSAService) ParseKeyPair(private, public []byte) error {
    privateKey, err := x509.ParseECPrivateKey(private)
    if err != nil {
        return err
    }

    service.privateKey = privateKey

    encodedKey := hex.EncodeToString(public)
    service.encodedPubKey = encodedKey

    publicKey, err := x509.ParsePKIXPublicKey(public)
    if err != nil {
        return err
    }

    service.publicKey = publicKey.(*ecdsa.PublicKey)

    return nil
}

// GenerateKeyPair will create a new key pair using ECDSA.
func (service *ECDSAService) GenerateKeyPair() ([]byte, []byte, error) {
    pubkeyCurve := elliptic.P256()

    privatekey, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
    if err != nil {
        return nil, nil, err
    }

    service.privateKey = privatekey
    service.publicKey = &privatekey.PublicKey

    private, err := x509.MarshalECPrivateKey(service.privateKey)
    if err != nil {
        return nil, nil, err
    }

    public, err := x509.MarshalPKIXPublicKey(service.publicKey)
    if err != nil {
        return nil, nil, err
    }

    encodedKey := hex.EncodeToString(public)
    service.encodedPubKey = encodedKey

    return private, public, nil
}

// CreateSignature creates a digital signature.
// It automatically hash a specific message using MD5 and creates a signature from
// that hash.
// If a secret is associated to the service, it will be used instead of the specified
// message.
// It then encodes the generated signature in base64.
func (service *ECDSAService) CreateSignature(message string) (string, error) {
    if service.secret != "" {
        message = service.secret
    }

    hash := libcrypto.HashFromBytes([]byte(message))

    r, s, err := ecdsa.Sign(rand.Reader, service.privateKey, hash)
    if err != nil {
        return "", err
    }

    keyBytes := service.privateKey.Params().BitSize / 8

    rBytes := r.Bytes()
    rBytesPadded := make([]byte, keyBytes)
    copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)

    sBytes := s.Bytes()
    sBytesPadded := make([]byte, keyBytes)
    copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)

    signature := append(rBytesPadded, sBytesPadded...)

    return base64.RawStdEncoding.EncodeToString(signature), nil
}