Open-CMSIS-Pack/cpackget

View on GitHub
cmd/cryptography/utils.go

Summary

Maintainability
A
0 mins
Test Coverage
F
0%
package cryptography

import (
    "archive/zip"
    "crypto/rsa"
    "crypto/sha256"
    "crypto/x509"
    "fmt"
    "hash"
    "strings"

    "github.com/ProtonMail/gopenpgp/v2/crypto"
    errs "github.com/open-cmsis-pack/cpackget/cmd/errors"
    "github.com/open-cmsis-pack/cpackget/cmd/utils"
    log "github.com/sirupsen/logrus"
)

// calculatePackHash hashes the contents of a zip file using the
// SHA256 algorithm and returns the underlying hash object.
func calculatePackHash(zip *zip.ReadCloser) ([]byte, error) {
    hashes := make([]byte, 0)
    h := sha256.New()
    for _, file := range zip.File {
        reader, err := file.Open()
        if err != nil {
            return nil, err
        }
        defer reader.Close()
        _, err = utils.SecureCopy(h, reader)
        if err != nil {
            return nil, err
        }
        hashes = h.Sum(hashes)
    }
    log.Debugf("Calculated hash: %s", fmt.Sprintf("%x", hashes))
    return hashes, nil
}

// detectKeyType identifies a PEM encoded RSA private key. It can be
// either PKCS1 or PKCS8, the latter not password-protected (std crypto
// does not support it currently).
func detectKeyType(key string) (string, error) {
    switch strings.Split(key, "-----")[1] {
    case "BEGIN RSA PRIVATE KEY":
        return "PKCS1", nil
    case "BEGIN PRIVATE KEY":
        return "PKCS8", nil
    case "BEGIN ENCRYPTED PRIVATE KEY":
        log.Error("encrypted PKCSC8 private keys aren't currently supported")
        return "", errs.ErrUnsupportedKeyAlgo
    default:
        log.Error("could not decode private key as PEM, please check for corruption")
        return "", errs.ErrBadPrivateKey
    }
}

// displayCertInfo prints the relevant fields of a certificate.
func displayCertificateInfo(cert *x509.Certificate) {
    log.Info("Loading relevant info from provided certificate")
    log.Info("To manually inspect it, use the --export/-e flag to export a copy")
    // This representation is loosely based on how Mozilla Firefox
    // represents certificate info (about:certificate?cert=...)
    log.Info("Subject:")
    log.Infof("    Country (C): %s", cert.Subject.Country)
    log.Infof("    Organization (O): %s", cert.Subject.Organization)
    log.Infof("    Common Name (CN): %s", cert.Subject.CommonName)
    log.Infof("    Alt Names: %s", cert.Subject.ExtraNames)
    log.Info("Issuer:")
    log.Infof("    Country (C): %s", cert.Issuer.Country)
    log.Infof("    Organization (O): %s", cert.Issuer.Organization)
    log.Infof("    Common Name (CN): %s", cert.Issuer.CommonName)
    log.Info("Validity:")
    log.Infof("    Not Valid Before: %s", cert.NotBefore)
    log.Infof("    Not Valid After: %s", cert.NotAfter)
    log.Info("Public key Info:")
    log.Infof("    Algorithm: %s", cert.PublicKeyAlgorithm.String())
    // Modulus size in bits, not bytes
    log.Infof("    Key Size: %d", cert.PublicKey.(*rsa.PublicKey).Size()*8)
    log.Infof("    Exponent: %d", cert.PublicKey.(*rsa.PublicKey).E)
    log.Info("Miscellaneous")
    log.Infof("    Signature Algorithm: %s", cert.SignatureAlgorithm.String())
    log.Infof("    Version: %d", cert.Version)
    log.Info("Basic Constraints")
    log.Infof("    Certificate Authority: %t", cert.IsCA)
    log.Info("Key Usages:")
    log.Infof("    Purposes: %s", getKeyUsage(cert.KeyUsage))
}

// getDigestList computes the digests of a pack according
// to the specified hash function.
func getDigestList(sourcePack, hashFunction string) (map[string]string, error) {
    var h hash.Hash
    switch hashFunction {
    case "sha256":
        h = sha256.New()
    } // Default will always be "sha256" if nothing is passed

    zipReader, err := zip.OpenReader(sourcePack)
    if err != nil {
        log.Errorf("can't decompress \"%s\": %s", sourcePack, err)
        return nil, errs.ErrFailedDecompressingFile
    }

    digests := make(map[string]string)
    for _, file := range zipReader.File {
        reader, err := file.Open()
        if err != nil {
            return nil, err
        }
        defer reader.Close()
        _, err = utils.SecureCopy(h, reader)
        if err != nil {
            return nil, err
        }
        digests[file.Name] = fmt.Sprintf("%x", h.Sum(nil))
    }
    return digests, nil
}

// getKeyUsage prints the RFC/human friendly version
// of possible X.509 key usages (https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3).
func getKeyUsage(k x509.KeyUsage) []string {
    getSingleUsage := func(k x509.KeyUsage) string {
        switch k {
        case x509.KeyUsageDigitalSignature:
            return "\"Digital Signature\""
        case x509.KeyUsageContentCommitment:
            return "\"Content Commitment\""
        case x509.KeyUsageKeyEncipherment:
            return "\"Key Encipherment\""
        case x509.KeyUsageDataEncipherment:
            return "\"Data Encipherment\""
        case x509.KeyUsageKeyAgreement:
            return "\"Key Agreement\""
        case x509.KeyUsageCertSign:
            return "\"Certificate Signing\""
        case x509.KeyUsageCRLSign:
            return "\"CRL Signing\""
        case x509.KeyUsageEncipherOnly:
            return "\"Encipher Only\""
        case x509.KeyUsageDecipherOnly:
            return "\"Decipher Only\""
        }
        return ""
    }

    // Simple bitmask "decoding"
    var uses []string
    for key := x509.KeyUsageDigitalSignature; key < 9; key <<= 1 {
        if k&key != 0 {
            uses = append(uses, getSingleUsage(key))
        }
    }
    return uses
}

// getUnlockedKeyring returns a ready to use
// KeyRing based on a private key.
func getUnlockedKeyring(key string, passphrase []byte) (*crypto.KeyRing, error) {
    privateKeyObj, err := crypto.NewKeyFromArmored(key)
    if err != nil {
        return nil, err
    }
    unlockedKeyObj, err := privateKeyObj.Unlock(passphrase)
    if err != nil {
        return nil, err
    }
    signingKeyRing, err := crypto.NewKeyRing(unlockedKeyObj)
    if err != nil {
        return nil, err
    }
    return signingKeyRing, nil
}

// isPrivateKeyFromCertificate tells whether a DER encoded key
// is the private counterpart to a X.509 certificate.
func isPrivateKeyFromCertificate(cert *x509.Certificate, keyDER []byte, keyType string) (bool, error) {
    if keyType == "PKCS1" {
        pv, err := x509.ParsePKCS1PrivateKey(keyDER)
        if err != nil {
            return false, err
        }
        pubCert := cert.PublicKey
        return pv.PublicKey.Equal(pubCert), nil
    } else {
        if keyType == "PKCS8" {
            pv, err := x509.ParsePKCS8PrivateKey(keyDER)
            if err != nil {
                return false, err
            }
            pubCert := cert.PublicKey
            return pv.(*rsa.PrivateKey).PublicKey.Equal(pubCert), nil
        }
    }
    return false, errs.ErrBadPrivateKey
}

// sanitizeVersionForSignature cleans the version string
// for the sig scheme as it may not be the same in builds
// from source vs an automated release.
func sanitizeVersionForSignature(version string) string {
    v := sigVersionPrefix
    if version != "" && string(version[0]) != "v" {
        return v + "v" + version
    }
    return v + version
}