// KeyToPEM returns a PEM encoded key from a Private Key func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) { if privKey.Cipher() != "RSA" { return nil, errors.New("only RSA keys are currently supported") } return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil }
// Create is used to generate keys for targets, snapshots and timestamps func (ccs *CryptoService) Create(role string, algorithm data.KeyAlgorithm) (data.PublicKey, error) { var privKey data.PrivateKey var err error switch algorithm { case data.RSAKey: privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize) if err != nil { return nil, fmt.Errorf("failed to generate RSA key: %v", err) } case data.ECDSAKey: privKey, err = trustmanager.GenerateECDSAKey(rand.Reader) if err != nil { return nil, fmt.Errorf("failed to generate EC key: %v", err) } case data.ED25519Key: privKey, err = trustmanager.GenerateED25519Key(rand.Reader) if err != nil { return nil, fmt.Errorf("failed to generate ED25519 key: %v", err) } default: return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm) } logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID()) // Store the private key into our keystore with the name being: /GUN/ID.key with an alias of role err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), role, privKey) if err != nil { return nil, fmt.Errorf("failed to add key to filestore: %v", err) } return data.PublicKeyFromPrivate(privKey), nil }
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey // and a passphrase func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) { var blockType string algorithm := key.Algorithm() switch algorithm { case data.RSAKey: blockType = "RSA PRIVATE KEY" case data.ECDSAKey: blockType = "EC PRIVATE KEY" default: return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm) } password := []byte(passphrase) cipherType := x509.PEMCipherAES256 encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, blockType, key.Private(), password, cipherType) if err != nil { return nil, err } return pem.EncodeToMemory(encryptedPEMBlock), nil }
// GenRootKey generates a new root key protected by a given passphrase // TODO(diogo): show not create keys manually, should use a cryptoservice instead func (km *KeyStoreManager) GenRootKey(algorithm, passphrase string) (string, error) { var err error var privKey *data.PrivateKey // We don't want external API callers to rely on internal TUF data types, so // the API here should continue to receive a string algorithm, and ensure // that it is downcased switch data.KeyAlgorithm(strings.ToLower(algorithm)) { case data.RSAKey: privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize) case data.ECDSAKey: privKey, err = trustmanager.GenerateECDSAKey(rand.Reader) default: return "", fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm) } if err != nil { return "", fmt.Errorf("failed to generate private key: %v", err) } // Changing the root km.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) return privKey.ID(), nil }
// KeyToPEM returns a PEM encoded key from a Private Key func KeyToPEM(privKey data.PrivateKey) ([]byte, error) { blockType, err := blockType(privKey.Algorithm()) if err != nil { return nil, err } return pem.EncodeToMemory(&pem.Block{Type: blockType, Bytes: privKey.Private()}), nil }
// Sign returns the signatures for the payload with a set of keyIDs. It ignores // errors to sign and expects the called to validate if the number of returned // signatures is adequate. func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { // Create hasher and hash data hash := crypto.SHA256 hashed := sha256.Sum256(payload) signatures := make([]data.Signature, 0, len(keyIDs)) for _, keyid := range keyIDs { // ccs.gun will be empty if this is the root key keyName := filepath.Join(ccs.gun, keyid) var privKey *data.PrivateKey var err error // Read PrivateKey from file and decrypt it if there is a passphrase. if ccs.passphrase != "" { privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase) } else { privKey, err = ccs.keyStore.GetKey(keyName) } if err != nil { // Note that GetDecryptedKey always fails on InitRepo. // InitRepo gets a signer that doesn't have access to // the root keys. Continuing here is safe because we // end up not returning any signatures. logrus.Debugf("ignoring error attempting to retrieve key ID: %s, %v", keyid, err) continue } algorithm := privKey.Algorithm() var sigAlgorithm data.SigAlgorithm var sig []byte switch algorithm { case data.RSAKey: sig, err = rsaSign(privKey, hash, hashed[:]) sigAlgorithm = data.RSAPSSSignature case data.ECDSAKey: sig, err = ecdsaSign(privKey, hashed[:]) sigAlgorithm = data.ECDSASignature } if err != nil { logrus.Debugf("ignoring error attempting to %s sign with keyID: %s, %v", algorithm, keyid, err) continue } logrus.Debugf("appending %s signature with Key ID: %s", algorithm, keyid) // Append signatures to result array signatures = append(signatures, data.Signature{ KeyID: keyid, Method: sigAlgorithm, Signature: sig[:], }) } return signatures, nil }
// KeyToPEM returns a PEM encoded key from a Private Key func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) { var pemType string algorithm := privKey.Algorithm() switch algorithm { case data.RSAKey: pemType = "RSA PRIVATE KEY" case data.ECDSAKey: pemType = "EC PRIVATE KEY" default: return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm) } return pem.EncodeToMemory(&pem.Block{Type: pemType, Bytes: privKey.Private()}), nil }
// Sign returns the signatures for the payload with a set of keyIDs. It ignores // errors to sign and expects the called to validate if the number of returned // signatures is adequate. func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { signatures := make([]data.Signature, 0, len(keyIDs)) for _, keyid := range keyIDs { // ccs.gun will be empty if this is the root key keyName := filepath.Join(ccs.gun, keyid) var privKey data.PrivateKey var err error privKey, _, err = ccs.keyStore.GetKey(keyName) if err != nil { logrus.Debugf("error attempting to retrieve key ID: %s, %v", keyid, err) return nil, err } algorithm := privKey.Algorithm() var sigAlgorithm data.SigAlgorithm var sig []byte switch algorithm { case data.RSAKey: sig, err = rsaSign(privKey, payload) sigAlgorithm = data.RSAPSSSignature case data.ECDSAKey: sig, err = ecdsaSign(privKey, payload) sigAlgorithm = data.ECDSASignature case data.ED25519Key: // ED25519 does not operate on a SHA256 hash sig, err = ed25519Sign(privKey, payload) sigAlgorithm = data.EDDSASignature } if err != nil { logrus.Debugf("ignoring error attempting to %s sign with keyID: %s, %v", algorithm, keyid, err) return nil, err } logrus.Debugf("appending %s signature with Key ID: %s", algorithm, keyid) // Append signatures to result array signatures = append(signatures, data.Signature{ KeyID: keyid, Method: sigAlgorithm, Signature: sig[:], }) } return signatures, nil }
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey // and a passphrase func EncryptPrivateKey(key data.PrivateKey, passphrase string) ([]byte, error) { blockType, err := blockType(key.Algorithm()) if err != nil { return nil, err } password := []byte(passphrase) cipherType := x509.PEMCipherAES256 encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, blockType, key.Private(), password, cipherType) if err != nil { return nil, err } return pem.EncodeToMemory(encryptedPEMBlock), nil }
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey // and a passphrase func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) { // TODO(diogo): Currently only supports RSA Private keys if key.Cipher() != "RSA" { return nil, errors.New("only RSA keys are currently supported") } password := []byte(passphrase) cipherType := x509.PEMCipherAES256 blockType := "RSA PRIVATE KEY" encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, blockType, key.Private(), password, cipherType) if err != nil { return nil, err } return pem.EncodeToMemory(encryptedPEMBlock), nil }
func ecdsaSign(privKey data.PrivateKey, hashed []byte) ([]byte, error) { if privKey.Algorithm() != data.ECDSAKey { return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm()) } // Create an ecdsa.PrivateKey out of the private key bytes ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private()) if err != nil { return nil, err } // Use the ECDSA key to sign the data r, s, err := ecdsa.Sign(rand.Reader, ecdsaPrivKey, hashed[:]) if err != nil { return nil, err } rBytes, sBytes := r.Bytes(), s.Bytes() octetLength := (ecdsaPrivKey.Params().BitSize + 7) >> 3 // MUST include leading zeros in the output rBuf := make([]byte, octetLength-len(rBytes), octetLength) sBuf := make([]byte, octetLength-len(sBytes), octetLength) rBuf = append(rBuf, rBytes...) sBuf = append(sBuf, sBytes...) return append(rBuf, sBuf...), nil }
func ed25519Sign(privKey data.PrivateKey, message []byte) ([]byte, error) { if privKey.Algorithm() != data.ED25519Key { return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm()) } priv := [ed25519.PrivateKeySize]byte{} copy(priv[:], privKey.Private()[ed25519.PublicKeySize:]) sig := ed25519.Sign(&priv, message) return sig[:], nil }
func rsaPKCS1v15Sign(privKey data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { if privKey.Algorithm() != data.RSAKey { return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm()) } // Create an rsa.PrivateKey out of the private key bytes rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKey.Private()) if err != nil { return nil, err } // Use the RSA key to RSAPKCS1v15 sign the data sig, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivKey, hash, hashed[:]) if err != nil { return nil, err } return sig, nil }
func rsaPSSSign(privKey data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { if privKey.Algorithm() != data.RSAKey { return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm()) } // Create an rsa.PrivateKey out of the private key bytes rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKey.Private()) if err != nil { return nil, err } // Use the RSA key to RSASSA-PSS sign the data sig, err := rsa.SignPSS(rand.Reader, rsaPrivKey, hash, hashed[:], &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) if err != nil { return nil, err } return sig, nil }
func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { // TODO(diogo): Implement support for ECDSA. if privKey.Cipher() != "RSA" { return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher()) } // Create an rsa.PrivateKey out of the private key bytes rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKey.Private()) if err != nil { return nil, err } // Use the RSA key to sign the data sig, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivKey, hash, hashed[:]) if err != nil { return nil, err } return sig, nil }
// addKey allows you to add a private key to the trust service func (trust *Ed25519) addKey(k *data.PrivateKey) { trust.keys[k.ID()] = k }
// addKey allows you to add a private key func (e *Ed25519) addKey(k data.PrivateKey) { e.keys[k.ID()] = k }
// AddKey stores the contents of a private key. Both name and alias are ignored, // we always use Key IDs as name, and don't support aliases func (s *KeyDBStore) AddKey(name, alias string, privKey data.PrivateKey) error { passphrase, _, err := s.retriever(privKey.ID(), s.defaultPassAlias, false, 1) if err != nil { return err } encryptedKey, err := jose.Encrypt(string(privKey.Private()), KeywrapAlg, EncryptionAlg, passphrase) if err != nil { return err } gormPrivKey := GormPrivateKey{ KeyID: privKey.ID(), EncryptionAlg: EncryptionAlg, KeywrapAlg: KeywrapAlg, PassphraseAlias: s.defaultPassAlias, Algorithm: privKey.Algorithm().String(), Public: string(privKey.Public()), Private: encryptedKey} // Add encrypted private key to the database s.db.Create(&gormPrivKey) // Value will be false if Create suceeds failure := s.db.NewRecord(gormPrivKey) if failure { return fmt.Errorf("failed to add private key to database: %s", privKey.ID()) } // Add the private key to our cache s.Lock() defer s.Unlock() s.cachedKeys[privKey.ID()] = privKey return nil }
func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" ts, mux := createTestServer(t) defer ts.Close() repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase") assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase") assert.NoError(t, err, "error retreiving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) // Add fixtures/intermediate-ca.crt as a target. There's no particular reason // for using this file except that it happens to be available as // a fixture. latestTarget, err := NewTarget("latest", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(latestTarget) assert.NoError(t, err, "error adding target") // Look for the changelist file changelistDirPath := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist") changelistDir, err := os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") fileInfos, err := changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") // Should only be one file in the directory assert.Len(t, fileInfos, 1, "wrong number of changelist files found") clName := fileInfos[0].Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName)) assert.NoError(t, err, "could not read changelist file %s", clName) c := &tufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName) assert.EqualValues(t, 0, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "latest", c.ChangePath) assert.NotEmpty(t, c.Data) changelistDir.Close() // Create a second target currentTarget, err := NewTarget("current", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(currentTarget) assert.NoError(t, err, "error adding target") changelistDir, err = os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") // There should now be a second file in the directory fileInfos, err = changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") assert.Len(t, fileInfos, 2, "wrong number of changelist files found") newFileFound := false for _, fileInfo := range fileInfos { if fileInfo.Name() != clName { clName2 := fileInfo.Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName2)) assert.NoError(t, err, "could not read changelist file %s", clName2) c := &tufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName2) assert.EqualValues(t, 0, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "current", c.ChangePath) assert.NotEmpty(t, c.Data) newFileFound = true break } } assert.True(t, newFileFound, "second changelist file not found") changelistDir.Close() // Now test ListTargets. In preparation, we need to expose some signed // metadata files on the internal HTTP server. // Apply the changelist. Normally, this would be done by Publish // load the changelist for this repo cl, err := changelist.NewFileChangelist(filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist")) assert.NoError(t, err, "could not open changelist") // apply the changelist to the repo err = applyChangelist(repo.tufRepo, cl) assert.NoError(t, err, "could not apply changelist") var tempKey data.PrivateKey json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey) repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), &tempKey) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/root.json", func(w http.ResponseWriter, r *http.Request) { rootJSONFile := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "metadata", "root.json") rootFileBytes, err := ioutil.ReadFile(rootJSONFile) assert.NoError(t, err) fmt.Fprint(w, string(rootFileBytes)) }) // Because ListTargets will clear this savedTUFRepo := repo.tufRepo mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.json", func(w http.ResponseWriter, r *http.Request) { signedTimestamp, err := savedTUFRepo.SignTimestamp(data.DefaultExpires("timestamp"), nil) assert.NoError(t, err) timestampJSON, _ := json.Marshal(signedTimestamp) fmt.Fprint(w, string(timestampJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/snapshot.json", func(w http.ResponseWriter, r *http.Request) { signedSnapshot, err := savedTUFRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) snapshotJSON, _ := json.Marshal(signedSnapshot) fmt.Fprint(w, string(snapshotJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/targets.json", func(w http.ResponseWriter, r *http.Request) { signedTargets, err := savedTUFRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) assert.NoError(t, err) targetsJSON, _ := json.Marshal(signedTargets) fmt.Fprint(w, string(targetsJSON)) }) targets, err := repo.ListTargets() assert.NoError(t, err) // Should be two targets assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets") if targets[0].Name == "latest" { assert.Equal(t, latestTarget, targets[0], "latest target does not match") assert.Equal(t, currentTarget, targets[1], "current target does not match") } else if targets[0].Name == "current" { assert.Equal(t, currentTarget, targets[0], "current target does not match") assert.Equal(t, latestTarget, targets[1], "latest target does not match") } else { t.Fatalf("unexpected target name: %s", targets[0].Name) } // Also test GetTargetByName newLatestTarget, err := repo.GetTargetByName("latest") assert.NoError(t, err) assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match") newCurrentTarget, err := repo.GetTargetByName("current") assert.NoError(t, err) assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match") }