// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package mage import ( "log" "regexp" "strconv" "strings" "sync" "github.com/magefile/mage/sh" "github.com/pkg/errors" ) var _appleKeychain = &appleKeychain{} type appleKeychain struct{} // SigningIdentity represents a key pair (public/private) that can be used for // signing. type SigningIdentity struct { ID string Description string } // ListIdentities queries the keychain to get a list of signing identities // (certificate + private key). func (k *appleKeychain) ListIdentities() ([]SigningIdentity, error) { var re = regexp.MustCompile(`(?m)^\s*\d+\)\s+(\w+)\s+"(.+)"$`) out, err := sh.Output("security", "find-identity", "-v") if err != nil { return nil, err } var idents []SigningIdentity ids := map[string]struct{}{} for _, match := range re.FindAllStringSubmatch(out, -1) { ident := SigningIdentity{ID: match[1], Description: match[2]} // Deduplicate if _, found := ids[ident.ID]; found { continue } idents = append(idents, ident) ids[ident.ID] = struct{}{} } return idents, nil } // AppleSigningInfo indicate if signing is enabled and specifies the identities // to use for signing applications and installers. type AppleSigningInfo struct { Sign bool App SigningIdentity Installer SigningIdentity } var ( appleSigningInfoValue *AppleSigningInfo appleSigningInfoErr error appleSigningInfoOnce sync.Once ) // GetAppleSigningInfo returns the signing identities used for code signing // apps and installers. // // Environment Variables // // APPLE_SIGNING_ENABLED - Must be set to true to enable signing. Defaults to // false. // // APPLE_SIGNING_IDENTITY_INSTALLER - filter for selecting the signing identity // for installers. // // APPLE_SIGNING_IDENTITY_APP - filter for selecting the signing identity // for apps. func GetAppleSigningInfo() (*AppleSigningInfo, error) { appleSigningInfoOnce.Do(func() { appleSigningInfoValue, appleSigningInfoErr = getAppleSigningInfo() }) return appleSigningInfoValue, appleSigningInfoErr } func getAppleSigningInfo() (*AppleSigningInfo, error) { var ( signingEnabled, _ = strconv.ParseBool(EnvOr("APPLE_SIGNING_ENABLED", "false")) identityInstaller = strings.ToLower(EnvOr("APPLE_SIGNING_IDENTITY_INSTALLER", "Developer ID Installer")) identityApp = strings.ToLower(EnvOr("APPLE_SIGNING_IDENTITY_APP", "Developer ID Application")) ) if !signingEnabled { return &AppleSigningInfo{Sign: false}, nil } idents, err := _appleKeychain.ListIdentities() if err != nil { return nil, err } var install, app []SigningIdentity for _, ident := range idents { id, desc := strings.ToLower(ident.ID), strings.ToLower(ident.Description) if strings.Contains(id, identityInstaller) || strings.Contains(desc, identityInstaller) { install = append(install, ident) } if strings.Contains(id, identityApp) || strings.Contains(desc, identityApp) { app = append(app, ident) } } if len(install) == 1 && len(app) == 1 { log.Printf("Apple Code Signing Identities:\n App: %+v\n Installer: %+v", app[0], install[0]) return &AppleSigningInfo{ Sign: true, Installer: install[0], App: app[0], }, nil } if len(install) > 1 { return nil, errors.Errorf("found multiple installer signing identities "+ "that match '%v'. Set a more specific APPLE_SIGNING_IDENTITY_INSTALLER "+ "value that will select one of %+v", identityInstaller, install) } if len(app) > 1 { return nil, errors.Errorf("found multiple installer signing identities "+ "that match '%v'. Set a more specific APPLE_SIGNING_IDENTITY_APP "+ "value that will select one of %+v", identityApp, app) } if len(install) == 0 || len(app) == 0 { return nil, errors.Errorf("apple signing was requested with " + "APPLE_SIGNING_ENABLED=true, but the required signing identities " + "for app and installer were not found") } return &AppleSigningInfo{Sign: false}, nil }