| // 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 |
| } |