Skip to content

Commit 3b22d85

Browse files
authored
Merge pull request #286 from ProtonMail/feat/v1-api-enforce-signature-hashes
RFC 9580 mandates the use of specific hash algorithms for certain signature algorithms. While the v2 API already enforces these restrictions, this PR ports the same logic to the v1 API to ensure that invalid or non-compliant data cannot be written.
2 parents 3c60603 + a9af95c commit 3b22d85

File tree

2 files changed

+101
-32
lines changed

2 files changed

+101
-32
lines changed

openpgp/v2/write.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ func (s signatureWriter) Close() error {
880880
return s.encryptedData.Close()
881881
}
882882

883-
func adaptHashToSigningKey(config *packet.Config, primary *packet.PublicKey) crypto.Hash {
883+
func selectHashForSigningKey(config *packet.Config, primary *packet.PublicKey) crypto.Hash {
884884
acceptableHashes := acceptableHashesToWrite(primary)
885885
hash, ok := algorithm.HashToHashId(config.Hash())
886886
if !ok {
@@ -893,17 +893,16 @@ func adaptHashToSigningKey(config *packet.Config, primary *packet.PublicKey) cry
893893
}
894894
if len(acceptableHashes) > 0 {
895895
defaultAcceptedHash, ok := algorithm.HashIdToHash(acceptableHashes[0])
896-
if !ok {
897-
return config.Hash()
896+
if ok {
897+
return defaultAcceptedHash
898898
}
899-
return defaultAcceptedHash
900899
}
901900
return config.Hash()
902901
}
903902

904903
func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature {
905904
sigLifetimeSecs := config.SigLifetime()
906-
hash := adaptHashToSigningKey(config, signer)
905+
hash := selectHashForSigningKey(config, signer)
907906
return &packet.Signature{
908907
Version: signer.Version,
909908
SigType: sigType,

openpgp/write.go

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -253,34 +253,12 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit
253253
}
254254

255255
var hash crypto.Hash
256-
for _, hashId := range candidateHashes {
257-
if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
258-
hash = h
259-
break
260-
}
261-
}
262-
263-
// If the hash specified by config is a candidate, we'll use that.
264-
if configuredHash := config.Hash(); configuredHash.Available() {
265-
for _, hashId := range candidateHashes {
266-
if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
267-
hash = h
268-
break
269-
}
270-
}
271-
}
272-
273-
if hash == 0 {
274-
hashId := candidateHashes[0]
275-
name, ok := algorithm.HashIdToString(hashId)
276-
if !ok {
277-
name = "#" + strconv.Itoa(int(hashId))
278-
}
279-
return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
280-
}
281-
282256
var salt []byte
283257
if signer != nil {
258+
if hash, err = selectHash(candidateHashes, config.Hash(), signer); err != nil {
259+
return nil, err
260+
}
261+
284262
var opsVersion = 3
285263
if signer.Version == 6 {
286264
opsVersion = signer.Version
@@ -558,13 +536,34 @@ func (s signatureWriter) Close() error {
558536
return s.encryptedData.Close()
559537
}
560538

539+
func selectHashForSigningKey(config *packet.Config, signer *packet.PublicKey) crypto.Hash {
540+
acceptableHashes := acceptableHashesToWrite(signer)
541+
hash, ok := algorithm.HashToHashId(config.Hash())
542+
if !ok {
543+
return config.Hash()
544+
}
545+
for _, acceptableHashes := range acceptableHashes {
546+
if acceptableHashes == hash {
547+
return config.Hash()
548+
}
549+
}
550+
if len(acceptableHashes) > 0 {
551+
defaultAcceptedHash, ok := algorithm.HashIdToHash(acceptableHashes[0])
552+
if ok {
553+
return defaultAcceptedHash
554+
}
555+
}
556+
return config.Hash()
557+
}
558+
561559
func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature {
562560
sigLifetimeSecs := config.SigLifetime()
561+
hash := selectHashForSigningKey(config, signer)
563562
return &packet.Signature{
564563
Version: signer.Version,
565564
SigType: sigType,
566565
PubKeyAlgo: signer.PubKeyAlgo,
567-
Hash: config.Hash(),
566+
Hash: hash,
568567
CreationTime: config.Now(),
569568
IssuerKeyId: &signer.KeyId,
570569
IssuerFingerprint: signer.Fingerprint,
@@ -618,3 +617,74 @@ func handleCompression(compressed io.WriteCloser, candidateCompression []uint8,
618617
}
619618
return data, nil
620619
}
620+
621+
// selectHash selects the preferred hash given the candidateHashes and the configuredHash
622+
func selectHash(candidateHashes []byte, configuredHash crypto.Hash, signer *packet.PrivateKey) (hash crypto.Hash, err error) {
623+
acceptableHashes := acceptableHashesToWrite(&signer.PublicKey)
624+
candidateHashes = intersectPreferences(acceptableHashes, candidateHashes)
625+
626+
for _, hashId := range candidateHashes {
627+
if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
628+
hash = h
629+
break
630+
}
631+
}
632+
633+
// If the hash specified by config is a candidate, we'll use that.
634+
if configuredHash.Available() {
635+
for _, hashId := range candidateHashes {
636+
if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
637+
hash = h
638+
break
639+
}
640+
}
641+
}
642+
643+
if hash == 0 {
644+
if len(acceptableHashes) > 0 {
645+
if h, ok := algorithm.HashIdToHash(acceptableHashes[0]); ok {
646+
hash = h
647+
} else {
648+
return 0, errors.UnsupportedError("no candidate hash functions are compiled in.")
649+
}
650+
} else {
651+
return 0, errors.UnsupportedError("no candidate hash functions are compiled in.")
652+
}
653+
}
654+
return
655+
}
656+
657+
func acceptableHashesToWrite(singingKey *packet.PublicKey) []uint8 {
658+
switch singingKey.PubKeyAlgo {
659+
case packet.PubKeyAlgoEd448:
660+
return []uint8{
661+
hashToHashId(crypto.SHA512),
662+
hashToHashId(crypto.SHA3_512),
663+
}
664+
case packet.PubKeyAlgoECDSA, packet.PubKeyAlgoEdDSA:
665+
if curve, err := singingKey.Curve(); err == nil {
666+
if curve == packet.Curve448 ||
667+
curve == packet.CurveNistP521 ||
668+
curve == packet.CurveBrainpoolP512 {
669+
return []uint8{
670+
hashToHashId(crypto.SHA512),
671+
hashToHashId(crypto.SHA3_512),
672+
}
673+
} else if curve == packet.CurveBrainpoolP384 ||
674+
curve == packet.CurveNistP384 {
675+
return []uint8{
676+
hashToHashId(crypto.SHA384),
677+
hashToHashId(crypto.SHA512),
678+
hashToHashId(crypto.SHA3_512),
679+
}
680+
}
681+
}
682+
}
683+
return []uint8{
684+
hashToHashId(crypto.SHA256),
685+
hashToHashId(crypto.SHA384),
686+
hashToHashId(crypto.SHA512),
687+
hashToHashId(crypto.SHA3_256),
688+
hashToHashId(crypto.SHA3_512),
689+
}
690+
}

0 commit comments

Comments
 (0)