blob: 9fdfcfbfdbc3cf1d0db757c7964714f1c797a234 [file] [log] [blame] [edit]
// Copyright 2020 The Fuchsia 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 artifactory
import (
"bytes"
"crypto/ed25519"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"path"
)
const (
// The name of the public key file to be uploaded with release builds.
releasePubkeyFilename = "publickey.pem"
signatureKey = "signature"
)
// Sign signs the upload files and attaches the signatures as metadata.
func Sign(uploads []Upload, privateKey ed25519.PrivateKey) ([]Upload, error) {
for i := range uploads {
data, err := ioutil.ReadFile(uploads[i].Source)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, fmt.Errorf("failed to read %s: %w", uploads[i].Source, err)
}
signature := base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, data))
if signature != "" {
uploads[i].Metadata = map[string]string{
signatureKey: signature,
}
}
}
return uploads, nil
}
// PrivateKey parses an ed25519 private key from a pem-formatted key file.
// It expects the key to be in a PKCS#8, ASN.1 DER format as generated by
// running `openssl genpkey -algorithm ed25519 -out <private key>.pem`.
func PrivateKey(keyPath string) (ed25519.PrivateKey, error) {
if keyPath == "" {
return nil, nil
}
pemData, err := ioutil.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read pkey: %w", err)
}
block, _ := pem.Decode(pemData)
if len(block.Bytes) == 0 {
return nil, fmt.Errorf("failed to decode private key")
}
// The decoded key should be in PKCS#8, ASN.1 DER form.
// We need to convert it to an ed25519.PrivateKey.
pkey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key from DER bytes: %w", err)
}
if _, ok := pkey.(ed25519.PrivateKey); !ok {
return nil, fmt.Errorf("private key is not of type ed25519")
}
return pkey.(ed25519.PrivateKey), nil
}
// PublicKeyUpload returns an Upload representing the public key used for release builds.
func PublicKeyUpload(namespace string, publicKey ed25519.PublicKey) (*Upload, error) {
if len(publicKey) == 0 {
return nil, fmt.Errorf("nil public key provided")
}
// Convert to PKIX, ASN.1 DER form to match what is generated by openssl.
pubkeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("failed to convert public key to DER form: %w", err)
}
block := &pem.Block{
Type: "PUBLIC KEY",
Bytes: pubkeyDER,
}
var data bytes.Buffer
if err := pem.Encode(&data, block); err != nil {
return nil, fmt.Errorf("failed to encode public key: %w", err)
}
return &Upload{
Destination: path.Join(namespace, releasePubkeyFilename),
Contents: data.Bytes(),
}, nil
}